diff --git a/.buildpacks b/.buildpacks index 13b8dbf8d2..1a1690f082 100644 --- a/.buildpacks +++ b/.buildpacks @@ -1,3 +1,2 @@ https://github.com/heroku/heroku-buildpack-nodejs.git#v183 -https://github.com/mars/create-react-app-inner-buildpack.git#v9.0.0 -https://github.com/heroku/heroku-buildpack-static.git#v5 \ No newline at end of file +https://github.com/heroku/heroku-buildpack-nginx.git \ No newline at end of file diff --git a/.github/workflows/cypress-testing.yml b/.github/workflows/cypress-testing.yml index 4b6e1ac15d..a389d87308 100644 --- a/.github/workflows/cypress-testing.yml +++ b/.github/workflows/cypress-testing.yml @@ -28,18 +28,12 @@ jobs: # Steps represent a sequence of tasks that will be executed as part of the job steps: # frontend setup - - name: Setup frontend + - name: Checkout frontend uses: actions/checkout@v3 - name: Use latest Node.js uses: actions/setup-node@v3 - - name: Setup elixir - uses: erlef/setup-beam@v1 - with: - elixir-version: ${{ matrix.elixir }} - otp-version: ${{ matrix.otp }} - - name: Setup frontend run: | echo copy env file. @@ -54,6 +48,12 @@ jobs: yarn setup echo done. + - name: Setup elixir + uses: erlef/setup-beam@v1 + with: + elixir-version: ${{ matrix.elixir }} + otp-version: ${{ matrix.otp }} + # backend setup - name: Setup backend run: | @@ -94,13 +94,9 @@ jobs: run: | echo clone cypress repo git clone https://github.com/glific/cypress-testing.git - echo done. go to dir. cd cypress-testing - cd .. - cp -r cypress-testing/cypress cypress - yarn add cypress@13.6.2 - echo Create cypress.config.ts from example - cp cypress-testing/cypress.config.ts.example cypress.config.ts + cp cypress.config.ts.example cypress.config.ts + yarn # Run frontend - name: Run glific frontend @@ -120,5 +116,6 @@ jobs: # Run cypress - name: Cypress run + working-directory: cypress-testing run: | yarn run cypress run --record --key ${{ secrets.CYPRESS_DASHBOARD_KEY }} diff --git a/.github/workflows/staging-testing.yml b/.github/workflows/staging-testing.yml new file mode 100644 index 0000000000..4ca156927d --- /dev/null +++ b/.github/workflows/staging-testing.yml @@ -0,0 +1,35 @@ +name: Staging Integration Testing + +on: + pull_request: + branches: [rvignesh/fix-nginx-conf] + +jobs: + cypress: + runs-on: ubuntu-latest + steps: + - name: Checkout frontend + uses: actions/checkout@v3 + + - name: Use latest Node.js + uses: actions/setup-node@v3 + + - name: Install dependencies + run: yarn setup + + - name: Setup Cypress + run: | + git clone https://github.com/glific/cypress-testing.git + cd cypress-testing + git checkout test_staging + cd .. + cp -r cypress-testing/cypress cypress + yarn add cypress@13.6.2 + cp cypress-testing/cypress.config.ts.example cypress.config.ts + + - name: Cypress run + env: + CYPRESS_BASE_URL: https://staging.glific.com + CYPRESS_BACKEND_URL: https://api.staging.glific.com/api + run: | + yarn run cypress run --record --key ${{ secrets.CYPRESS_DASHBOARD_KEY }} \ No newline at end of file diff --git a/Procfile b/Procfile new file mode 100644 index 0000000000..d545c9d818 --- /dev/null +++ b/Procfile @@ -0,0 +1 @@ +web: bin/start-nginx-static \ No newline at end of file diff --git a/config/nginx.conf.erb b/config/nginx.conf.erb new file mode 100644 index 0000000000..4043bd1764 --- /dev/null +++ b/config/nginx.conf.erb @@ -0,0 +1,41 @@ +daemon off; + +worker_processes <%= ENV['NGINX_WORKERS'] || 4 %>; + +pid /app/nginx.pid; +error_log stderr error; + +events { + use epoll; + accept_mutex on; + worker_connections 1024; +} + +http { + include mime.types; + gzip on; + gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript; + + log_format l2met 'measure#nginx.service=$request_time request_id=$http_x_request_id'; + access_log /dev/stdout l2met; + + server { + listen <%= ENV['PORT'] %>; + root /app/build; + index index.html; + + add_header X-Content-Type-Options "nosniff" always; + add_header X-XSS-Protection "1; mode=block" always; + add_header X-Frame-Options "deny" always; + add_header Strict-Transport-Security "max-age=63072000; includeSubdomains; preload" always; + add_header Content-Security-Policy "default-src * data:; script-src 'self' 'unsafe-inline' 'unsafe-eval' blob:; script-src-elem 'self' 'unsafe-inline' https://www.google.com https://www.gstatic.com https://js.stripe.com; frame-src 'self' https://js.stripe.com https://www.canva.com https://www.google.com https://www.gstatic.com data:; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' data: https://fonts.gstatic.com; connect-src *;" always; + + if ($http_x_forwarded_proto != "https") { + return 301 https://$host$request_uri; + } + + location / { + try_files $uri $uri/ /index.html; + } + } +} \ No newline at end of file diff --git a/package.json b/package.json index d6e8f260d5..bbb2f137d1 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "glific-frontend", - "version": "6.9.0", + "version": "6.9.1", "private": true, "type": "module", "license": { diff --git a/src/containers/Auth/Auth.tsx b/src/containers/Auth/Auth.tsx index d170fe8c8e..4bc01fe8fa 100644 --- a/src/containers/Auth/Auth.tsx +++ b/src/containers/Auth/Auth.tsx @@ -15,8 +15,6 @@ import setLogs from 'config/logs'; import { checkOrgStatus } from 'services/AuthService'; import { TERMS_OF_USE_LINK } from 'common/constants'; -import { Promotion } from './Promotion/Promotion'; - export interface AuthProps { pageTitle: string; buttonText: string; @@ -302,8 +300,6 @@ export const Auth = ({ ) : null} - - {mode === 'login' && } ); }; diff --git a/src/containers/Chat/ChatConversations/ChatConversation/ChatConversation.test.tsx b/src/containers/Chat/ChatConversations/ChatConversation/ChatConversation.test.tsx index a7555614bf..fd1a6f913b 100644 --- a/src/containers/Chat/ChatConversations/ChatConversation/ChatConversation.test.tsx +++ b/src/containers/Chat/ChatConversations/ChatConversation/ChatConversation.test.tsx @@ -1,10 +1,10 @@ +import { MockedProvider } from '@apollo/client/testing'; import { fireEvent, render } from '@testing-library/react'; import dayjs from 'dayjs'; -import { MockedProvider } from '@apollo/client/testing'; import { MemoryRouter } from 'react-router'; -import { MARK_AS_READ } from 'graphql/mutations/Chat'; import { SHORT_DATE_FORMAT } from 'common/constants'; +import { MARK_AS_READ } from 'graphql/mutations/Chat'; import ChatConversation from './ChatConversation'; const mockCallback = vi.fn(); @@ -79,3 +79,50 @@ test('it should call the callback function on click action', () => { fireEvent.click(getAllByTestId('list')[0]); expect(mockCallback).toHaveBeenCalled(); }); + +test('it should not throw when lastMessage body is null', () => { + const props = { + ...defaultProps, + highlightSearch: 'test', + lastMessage: { body: null, insertedAt, type: 'TEXT' }, + }; + expect(() => render(wrapperContainer(props))).not.toThrow(); +}); + +test('it should not throw when lastMessage body is undefined', () => { + const props = { + ...defaultProps, + highlightSearch: 'test', + lastMessage: { body: undefined, insertedAt, type: 'TEXT' }, + }; + expect(() => render(wrapperContainer(props))).not.toThrow(); +}); + +test('it should truncate message body longer than 35 characters', () => { + const longBody = 'This is a very long message that exceeds the limit'; + const props = { + ...defaultProps, + lastMessage: { body: longBody, insertedAt, type: 'TEXT' }, + }; + const { getByTestId } = render(wrapperContainer(props)); + expect(getByTestId('content').textContent).toContain('...'); +}); + +test('it should not truncate message body within 35 characters', () => { + const shortBody = 'Short message'; + const props = { + ...defaultProps, + lastMessage: { body: shortBody, insertedAt, type: 'TEXT' }, + }; + const { getByTestId } = render(wrapperContainer(props)); + expect(getByTestId('content').textContent).not.toContain('...'); +}); + +test('it should replace newlines with spaces in TEXT messages', () => { + const props = { + ...defaultProps, + lastMessage: { body: 'Hello\nWorld', insertedAt, type: 'TEXT' }, + }; + const { getByTestId } = render(wrapperContainer(props)); + expect(getByTestId('content').textContent).not.toContain('\n'); +}); diff --git a/src/containers/Chat/ChatConversations/ChatConversation/ChatConversation.tsx b/src/containers/Chat/ChatConversations/ChatConversation/ChatConversation.tsx index 0a16abc485..e14f335e54 100644 --- a/src/containers/Chat/ChatConversations/ChatConversation/ChatConversation.tsx +++ b/src/containers/Chat/ChatConversations/ChatConversation/ChatConversation.tsx @@ -1,18 +1,18 @@ +import { useApolloClient, useMutation } from '@apollo/client'; import { ListItemButton } from '@mui/material'; -import { Link, useLocation } from 'react-router'; import dayjs from 'dayjs'; -import { useApolloClient, useMutation } from '@apollo/client'; +import { Link, useLocation } from 'react-router'; import { COMPACT_MESSAGE_LENGTH, SHORT_DATE_FORMAT } from 'common/constants'; -import { MARK_AS_READ } from 'graphql/mutations/Chat'; -import { SEARCH_OFFSET } from 'graphql/queries/Search'; import { WhatsAppToJsx } from 'common/RichEditor'; -import { MessageType } from '../MessageType/MessageType'; -import styles from './ChatConversation.module.css'; -import Track from 'services/TrackService'; import { slicedString, updateContactCache } from 'common/utils'; import { AvatarDisplay } from 'components/UI/AvatarDisplay/AvatarDisplay'; import { Timer } from 'components/UI/Timer/Timer'; +import { MARK_AS_READ } from 'graphql/mutations/Chat'; +import { SEARCH_OFFSET } from 'graphql/queries/Search'; +import Track from 'services/TrackService'; +import { MessageType } from '../MessageType/MessageType'; +import styles from './ChatConversation.module.css'; export interface ChatConversationProps { entityId: number; @@ -24,7 +24,7 @@ export interface ChatConversationProps { index: number; lastMessage: { id: number; - body: string; + body: string | null | undefined; insertedAt: string; type: string; media: any; @@ -130,13 +130,11 @@ const ChatConversation = ({ } const name = slicedString(contactName, 20); - const { type, body } = lastMessage; const isTextType = type === 'TEXT'; + let originalText = body ?? ''; + let displayMSG: any = ; - let displayMSG: any = ; - - let originalText = body; if (isTextType) { // let's shorten the text message to display correctly if (originalText.length > COMPACT_MESSAGE_LENGTH) { @@ -203,7 +201,7 @@ const ChatConversation = ({ {name}
- {isTextType && highlightSearch ? BoldedText(body, highlightSearch) : displayMSG} + {isTextType && highlightSearch ? BoldedText(originalText, highlightSearch) : displayMSG}
diff --git a/static.json b/static.json deleted file mode 100644 index 9895b628d7..0000000000 --- a/static.json +++ /dev/null @@ -1,16 +0,0 @@ -{ - "root": "build/", - "routes": { - "/**": "index.html" - }, - "https_only": true, - "headers": { - "/**": { - "X-Content-Type-Options": "nosniff", - "X-XSS-Protection": "1; mode=block", - "X-Frame-Options": "deny", - "Content-Security-Policy": "default-src * data:; script-src 'self' 'unsafe-inline' 'unsafe-eval' blob:; script-src-elem 'self' 'unsafe-inline' https://www.google.com https://www.gstatic.com https://js.stripe.com; frame-src 'self' https://js.stripe.com https://www.canva.com https://www.google.com https://www.gstatic.com data:; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' data: https://fonts.gstatic.com; connect-src *;", - "Strict-Transport-Security": "max-age=63072000; includeSubdomains; preload" - } - } -}