Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(invitation): Improve invitation flow - Milestone 2 #6804

Merged

Conversation

AMoreaux
Copy link
Contributor

@AMoreaux AMoreaux commented Aug 30, 2024

From PR: #6626
Resolves #6763
Resolves #6055
Resolves #6782

GTK

I retain the 'Invite by link' feature to prevent any breaking changes. We could make the invitation by link optional through an admin setting, allowing users to rely solely on personal invitations.

Todo

  • Add an expiration date to an invitation
  • Allow to renew an invitation to postpone the expiration date
  • Refresh the UI
  • Add the new personal token in the link sent to new user
  • Display an error if a user tries to use an expired invitation
  • Display an error if a user uses another mail than the one in the invitation

Copy link

github-actions bot commented Aug 30, 2024

Fails
🚫

node failed.

Log

�[31mError: �[39m RequestError [HttpError]: You have exceeded a secondary rate limit. Please wait a few minutes before you try again. If you reach out to GitHub Support for help, please include the request ID 4437:22D6B:5626D3B:A61D553:66EB4603.
    at /home/runner/work/twenty/twenty/node_modules/�[4m@octokit�[24m/request/dist-node/index.js:86:21
�[90m    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)�[39m {
  status: �[33m403�[39m,
  response: {
    url: �[32m'https://api.github.com/search/issues?q=is%3Apr%20author%3AAMoreaux%20is%3Aclosed%20repo%3Atwentyhq%2Ftwenty&per_page=2&page=1'�[39m,
    status: �[33m403�[39m,
    headers: {
      �[32m'access-control-allow-origin'�[39m: �[32m'*'�[39m,
      �[32m'access-control-expose-headers'�[39m: �[32m'ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Used, X-RateLimit-Resource, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type, X-GitHub-SSO, X-GitHub-Request-Id, Deprecation, Sunset'�[39m,
      connection: �[32m'close'�[39m,
      �[32m'content-encoding'�[39m: �[32m'gzip'�[39m,
      �[32m'content-security-policy'�[39m: �[32m"default-src 'none'"�[39m,
      �[32m'content-type'�[39m: �[32m'application/json; charset=utf-8'�[39m,
      date: �[32m'Wed, 18 Sep 2024 21:28:35 GMT'�[39m,
      �[32m'referrer-policy'�[39m: �[32m'origin-when-cross-origin, strict-origin-when-cross-origin'�[39m,
      server: �[32m'github.com'�[39m,
      �[32m'strict-transport-security'�[39m: �[32m'max-age=31536000; includeSubdomains; preload'�[39m,
      �[32m'transfer-encoding'�[39m: �[32m'chunked'�[39m,
      vary: �[32m'Accept-Encoding, Accept, X-Requested-With'�[39m,
      �[32m'x-content-type-options'�[39m: �[32m'nosniff'�[39m,
      �[32m'x-frame-options'�[39m: �[32m'deny'�[39m,
      �[32m'x-github-api-version-selected'�[39m: �[32m'2022-11-28'�[39m,
      �[32m'x-github-media-type'�[39m: �[32m'github.v3; format=json'�[39m,
      �[32m'x-github-request-id'�[39m: �[32m'4437:22D6B:5626D3B:A61D553:66EB4603'�[39m,
      �[32m'x-ratelimit-limit'�[39m: �[32m'30'�[39m,
      �[32m'x-ratelimit-remaining'�[39m: �[32m'30'�[39m,
      �[32m'x-ratelimit-reset'�[39m: �[32m'1726694975'�[39m,
      �[32m'x-ratelimit-resource'�[39m: �[32m'search'�[39m,
      �[32m'x-ratelimit-used'�[39m: �[32m'1'�[39m,
      �[32m'x-xss-protection'�[39m: �[32m'0'�[39m
    },
    data: {
      documentation_url: �[32m'https://docs.github.com/free-pro-team@latest/rest/overview/rate-limits-for-the-rest-api#about-secondary-rate-limits'�[39m,
      message: �[32m'You have exceeded a secondary rate limit. Please wait a few minutes before you try again. If you reach out to GitHub Support for help, please include the request ID 4437:22D6B:5626D3B:A61D553:66EB4603.'�[39m
    }
  },
  request: {
    method: �[32m'GET'�[39m,
    url: �[32m'https://api.github.com/search/issues?q=is%3Apr%20author%3AAMoreaux%20is%3Aclosed%20repo%3Atwentyhq%2Ftwenty&per_page=2&page=1'�[39m,
    headers: {
      accept: �[32m'application/vnd.github.v3+json'�[39m,
      �[32m'user-agent'�[39m: �[32m'octokit-rest.js/18.12.0 octokit-core.js/3.6.0 Node.js/18.20.4 (linux; x64)'�[39m,
      authorization: �[32m'token [REDACTED]'�[39m
    },
    request: { hook: �[36m[Function: bound bound register]�[39m }
  }
}
danger-results://tmp/danger-results-81e2e2e2.json

Generated by 🚫 dangerJS against edad971

@AMoreaux AMoreaux force-pushed the feat/allow-usage-of-personal-invitation branch 2 times, most recently from 3ff8377 to 6e1d51b Compare August 30, 2024 15:17
@AMoreaux AMoreaux force-pushed the feat/allow-usage-of-personal-invitation branch 4 times, most recently from eaaa88e to f46d4fd Compare September 12, 2024 15:17
@AMoreaux AMoreaux marked this pull request as ready for review September 12, 2024 15:19
@AMoreaux
Copy link
Contributor Author

@FelixMalfait How can I test the Google Connect and the Microsoft connect feature?

@AMoreaux AMoreaux force-pushed the feat/allow-usage-of-personal-invitation branch from f46d4fd to 0ea8493 Compare September 12, 2024 15:31
Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR Summary

This pull request implements significant improvements to the invitation flow, focusing on personal invitations with expiration dates and better management of workspace members. Here's a summary of the key changes:

  • Added support for personal invitation tokens in the sign-up process
  • Implemented expiration dates for invitations with the ability to renew them
  • Updated the UI to display, manage, and resend workspace invitations
  • Improved error handling for invalid or expired invitations
  • Modified database schema to support the new invitation system
  • Enhanced the invitation process for both email and social login methods

Key points:

  • New AppTokenType.InvitationToken added to support personal invitations
  • WorkspaceInviteTeam component now uses useInviteUser hook for improved invitation handling
  • SettingsWorkspaceMembers component significantly refactored to manage invitations and members
  • Added GraphQL mutations for deleting and resending workspace invitations
  • Implemented useWorkspaceFromInviteHash hook to handle invitation logic consistently

29 file(s) reviewed, 13 comment(s)
Edit PR Review Bot Settings

@@ -295,21 +297,44 @@ export const useAuth = () => {
[setIsVerifyPendingState, signUp, handleVerify],
);

const handleGoogleLogin = useCallback((workspaceInviteHash?: string) => {
// TODO: how to test that?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: Remove TODO comment and implement proper testing for this function

packages/twenty-front/src/pages/auth/Invite.tsx Outdated Show resolved Hide resolved
);

await queryRunner.query(
`ALTER TABLE core."appToken" ADD CONSTRAINT "userIdNotNullWhenTypeIsNotInvitation" CHECK ("appToken".type = 'INVITATION_TOKEN' OR "appToken"."userId" NOTNULL)`,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

syntax: NOTNULL is non-standard SQL. Use IS NOT NULL instead.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agree with greptile!

@AMoreaux AMoreaux force-pushed the feat/allow-usage-of-personal-invitation branch 4 times, most recently from 9ceb7c4 to 412af8a Compare September 12, 2024 15:49
@AMoreaux AMoreaux force-pushed the feat/allow-usage-of-personal-invitation branch from 15cb5e4 to a1c9f4f Compare September 17, 2024 09:12
@AMoreaux AMoreaux force-pushed the feat/allow-usage-of-personal-invitation branch from c809c4c to 59903c3 Compare September 18, 2024 10:20

import { useAuth } from '@/auth/hooks/useAuth';

export const useSignInWithMicrosoft = () => {
const workspaceInviteHash = useParams().workspaceInviteHash;
const [searchParams] = useSearchParams();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM but I feel this could have been extracted to a hook as it's duplicated 3 times!

@@ -41,10 +43,12 @@ export const TableRow = ({
to,
className,
children,
gridAutoColumns,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel we could have found a better naming, it's a bit too "technical" from the outside point of view

export const useCreateWorkspaceInvitation = () => {
const [sendInvitationsMutation] = useSendInvitationsMutation();

const [, setWorkspaceInvitations] = useRecoilState(workspaceInvitationsState);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

useSetRecoilState!

Unfortunately, if you use useRecoilState you are doing an underlying useRecoilValue and useSetRecoilState even if you don't use the value

const [deleteWorkspaceInvitationMutation] =
useDeleteWorkspaceInvitationMutation();

const [, setWorkspaceInvitations] = useRecoilState(workspaceInvitationsState);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here

const [resendWorkspaceInvitationMutation] =
useResendWorkspaceInvitationMutation();

const [, setWorkspaceInvitations] = useRecoilState(workspaceInvitationsState);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

idem

@@ -24,3 +24,10 @@ export type WorkspaceMember = {
dateFormat?: WorkspaceMemberDateFormatEnum | null;
timeFormat?: WorkspaceMemberTimeFormatEnum | null;
};

export type WorkspaceInvitation = {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this could be in workspace-invitation module

import { isDefined } from '~/utils/isDefined';

const StyledContainer = styled.div`
display: flex;
flex-direction: row;
padding-bottom: 12px;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use theme here!

`;

const StyledTableHeaderRow = styled(Table)`
margin-bottom: ${({ theme }) => theme.spacing(1.5)};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's a bit a code smell that you are using .5 units
Usually if you have that is that the design meant that the height was important and not the margin

private readonly userWorkspaceService: UserWorkspaceService,
private readonly workspaceInvitationService: WorkspaceInvitationService,
) {}

@Mutation(() => User)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we will likely need to rename this mutation to be consistent
addUserToWorkspaceByInviteHash

) {}

onModuleInit() {
this.tokenService = this.moduleRef.get(TokenService, { strict: false });
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this does not look standard, why not using dependency injection?

Copy link
Member

@charlesBochet charlesBochet left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a very good one!
Congrats on this huge PR and thanks for all the attention to details, for following our project guidelines and for the fix post reviews!

I have added small comments that are non merge blocking and push little changes so we can merge it as you have been waiting long enough :p

.getOne();
}

castAppTokenToWorkspaceInvitation(appToken: AppToken) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could be extracted to an util

@@ -0,0 +1,17 @@
import { CustomException } from 'src/utils/custom-exception';

export class WorkspaceInvitationException extends CustomException {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nice!

color: ${({ theme, color }) => color ?? theme.font.color.primary};
`;

export const StyledText = ({
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

usually we don't export component named "Styled" even if re-usable. Outside consumers do not care it's a StyledComponent or not!

@charlesBochet charlesBochet force-pushed the feat/allow-usage-of-personal-invitation branch from fdef1e5 to edad971 Compare September 18, 2024 21:27
@charlesBochet charlesBochet merged commit 89c9799 into twentyhq:main Sep 18, 2024
2 of 3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment