From 824c9bfdc76385e64d4550a54adacdce1c0584d3 Mon Sep 17 00:00:00 2001 From: Jae_Philip_Yang Date: Thu, 2 Nov 2023 13:14:20 +0900 Subject: [PATCH 1/6] =?UTF-8?q?[BE]=20SSE=20=EC=97=B0=EA=B2=B0=EC=8B=9C=20?= =?UTF-8?q?=EC=BC=80=EC=8B=9C=EC=A0=84=EC=86=A1=20=EC=A0=9C=EA=B1=B0=20?= =?UTF-8?q?=EB=B0=8F=20Keep-Alive=20=EC=84=A4=EC=A0=95=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20(#847)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: SSE send threadpool Cached -> fiexed with 100 thread * refactor: SseConnection 연결시 keep-alive 헤더 추가 * feat: SSE 이벤트 캐시 기능 제거 * style: 오타 수정 * fix: Async테스트시 메서드 실행 기다린 후 오류방지를 위해 잠시 대기 --- .../sse/application/SseSubscribeService.java | 16 ----- .../application/TeamPlaceSsePublisher.java | 1 - .../teambyteam/sse/domain/SseEmitters.java | 3 +- .../domain/TeamPlaceEmitterRepository.java | 42 ------------- .../sse/presentation/SseController.java | 4 ++ .../acceptance/IcalendarAcceptanceTest.java | 10 +-- .../application/SseSubscribeServiceTest.java | 61 ------------------- .../TeamPlaceEmitterRepositoryTest.java | 54 ---------------- 8 files changed, 12 insertions(+), 179 deletions(-) diff --git a/backend/src/main/java/team/teamby/teambyteam/sse/application/SseSubscribeService.java b/backend/src/main/java/team/teamby/teambyteam/sse/application/SseSubscribeService.java index 1fa1d2fbf..0c9cd6d0f 100644 --- a/backend/src/main/java/team/teamby/teambyteam/sse/application/SseSubscribeService.java +++ b/backend/src/main/java/team/teamby/teambyteam/sse/application/SseSubscribeService.java @@ -54,23 +54,7 @@ public SseEmitter subscribe(final Long teamPlaceId, final MemberEmailDto memberE final TeamPlaceEventId dummyEventId = TeamPlaceEventId.of(teamPlaceId, dummyEvent.getEventName()); emitter.sendEvent(dummyEventId, dummyEvent); - if (!Objects.isNull(lastEventId) && !lastEventId.isBlank()) { - sendCachedEvents(emitter, teamPlaceId, TeamPlaceEventId.from(lastEventId)); - } - log.info("SSE 연결 생성 {}", emitterId); return emitter.getSingleEmitter(); } - - private void sendCachedEvents( - final SseEmitters emitter, - final Long teamPlaceId, - final TeamPlaceEventId lastEventId - ) { - final Map events = teamplaceEmitterRepository.findAllEventCacheWithId(teamPlaceId); - events.entrySet().stream() - .filter(entry -> entry.getKey().isPublishedAfter(lastEventId)) - .sorted(EVENT_ID_TIME_COMPARATOR) - .forEach(entry -> emitter.sendEvent(entry.getKey(), entry.getValue())); - } } diff --git a/backend/src/main/java/team/teamby/teambyteam/sse/application/TeamPlaceSsePublisher.java b/backend/src/main/java/team/teamby/teambyteam/sse/application/TeamPlaceSsePublisher.java index 23ec7e54c..27290649a 100644 --- a/backend/src/main/java/team/teamby/teambyteam/sse/application/TeamPlaceSsePublisher.java +++ b/backend/src/main/java/team/teamby/teambyteam/sse/application/TeamPlaceSsePublisher.java @@ -25,6 +25,5 @@ public void publishEvent(final TeamPlaceSseEvent teamPlaceSseEvent) { final SseEmitters emitters = teamPlaceEmitterRepository.findByTeamPlaceId(targetTeamPlaceId); emitters.sendEvent(eventId, teamPlaceSseEvent); - teamPlaceEmitterRepository.addEventCache(eventId, teamPlaceSseEvent.getEvent()); } } diff --git a/backend/src/main/java/team/teamby/teambyteam/sse/domain/SseEmitters.java b/backend/src/main/java/team/teamby/teambyteam/sse/domain/SseEmitters.java index 13d3f4e02..394e03ccf 100644 --- a/backend/src/main/java/team/teamby/teambyteam/sse/domain/SseEmitters.java +++ b/backend/src/main/java/team/teamby/teambyteam/sse/domain/SseEmitters.java @@ -14,7 +14,8 @@ public class SseEmitters { private static final ObjectMapper objectMapper = new ObjectMapper(); - private static final ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) Executors.newCachedThreadPool(); + private static final int NUMBER_OF_THREAD = 100; + private static final ThreadPoolExecutor threadPoolExecutor = (ThreadPoolExecutor) Executors.newFixedThreadPool(NUMBER_OF_THREAD); private final Map emitters; diff --git a/backend/src/main/java/team/teamby/teambyteam/sse/domain/TeamPlaceEmitterRepository.java b/backend/src/main/java/team/teamby/teambyteam/sse/domain/TeamPlaceEmitterRepository.java index d95abd0ba..c307958ec 100644 --- a/backend/src/main/java/team/teamby/teambyteam/sse/domain/TeamPlaceEmitterRepository.java +++ b/backend/src/main/java/team/teamby/teambyteam/sse/domain/TeamPlaceEmitterRepository.java @@ -1,11 +1,9 @@ package team.teamby.teambyteam.sse.domain; import lombok.extern.slf4j.Slf4j; -import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; -import java.time.LocalDateTime; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; @@ -17,7 +15,6 @@ public class TeamPlaceEmitterRepository { private static final int CACHE_REMOVE_MINUTE = 1; private final Map emitters = new ConcurrentHashMap<>(); - private final EventCache eventCache = new EventCache(); public SseEmitters save(final TeamPlaceEmitterId emitterId, final SseEmitter sseEmitter) { emitters.put(emitterId, sseEmitter); @@ -52,43 +49,4 @@ public SseEmitters findByTeamPlaceId(final Long teamPlaceId) { public void deleteById(final TeamPlaceEmitterId id) { emitters.remove(id); } - - public Map findAllEventCacheWithId(final Long teamPlaceId) { - return eventCache.findAllByTeamPlaceId(teamPlaceId); - } - - public void addEventCache(final TeamPlaceEventId teamPlaceEventId, final Object event) { - eventCache.put(teamPlaceEventId, event); - } - - @Scheduled(fixedDelayString = "${sse.cache-schedule-period}") - private void cacheRefreshSchedule() { - eventCache.clearCacheFor(CACHE_REMOVE_MINUTE); - } - - private static class EventCache { - - private final Map cache = new ConcurrentHashMap<>(); - - public Map findAllByTeamPlaceId(final Long teamPlaceId) { - return cache.entrySet() - .stream() - .filter(entry -> entry.getKey().isPublishedTo(teamPlaceId)) - .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); - } - - public void put(final TeamPlaceEventId teamPlaceEventId, final Object event) { - cache.put(teamPlaceEventId, event); - } - - public void clearCacheFor(final int minutes) { - final LocalDateTime now = LocalDateTime.now(); - cache.keySet().forEach(key -> { - if (key.getTimeStamp().isBefore(now.minusMinutes(minutes))) { - cache.remove(key); - } - } - ); - } - } } diff --git a/backend/src/main/java/team/teamby/teambyteam/sse/presentation/SseController.java b/backend/src/main/java/team/teamby/teambyteam/sse/presentation/SseController.java index ce561e438..a349051fd 100644 --- a/backend/src/main/java/team/teamby/teambyteam/sse/presentation/SseController.java +++ b/backend/src/main/java/team/teamby/teambyteam/sse/presentation/SseController.java @@ -20,6 +20,9 @@ public class SseController { private static final String NGINX_X_ACCEL_BUFFERING_HEADER = "X-Accel-Buffering"; private static final String NO = "no"; + private static final String KEEP_ALIVE = "Keep-Alive"; + private static final String TIMEOUT = "timout="; + private static final int TIMEOUT_VALUE = 5*60; private final SseSubscribeService sseSubscribeService; @@ -33,6 +36,7 @@ public ResponseEntity connect( return ResponseEntity .ok() .header(NGINX_X_ACCEL_BUFFERING_HEADER, NO) + .header(KEEP_ALIVE, TIMEOUT + TIMEOUT_VALUE) .body(emitter); } } diff --git a/backend/src/test/java/team/teamby/teambyteam/icalendar/acceptance/IcalendarAcceptanceTest.java b/backend/src/test/java/team/teamby/teambyteam/icalendar/acceptance/IcalendarAcceptanceTest.java index c84153311..dca968b16 100644 --- a/backend/src/test/java/team/teamby/teambyteam/icalendar/acceptance/IcalendarAcceptanceTest.java +++ b/backend/src/test/java/team/teamby/teambyteam/icalendar/acceptance/IcalendarAcceptanceTest.java @@ -1,11 +1,7 @@ package team.teamby.teambyteam.icalendar.acceptance; -import static org.mockito.ArgumentMatchers.any; -import static team.teamby.teambyteam.common.fixtures.acceptance.IcalendarAcceptanceFixtures.GET_ICALENDAR_PUBLISHED_URL; - import io.restassured.response.ExtractableResponse; import io.restassured.response.Response; -import java.util.concurrent.CountDownLatch; import org.aspectj.lang.annotation.After; import org.aspectj.lang.annotation.Aspect; import org.assertj.core.api.SoftAssertions; @@ -28,6 +24,11 @@ import team.teamby.teambyteam.member.domain.Member; import team.teamby.teambyteam.teamplace.domain.TeamPlace; +import java.util.concurrent.CountDownLatch; + +import static org.mockito.ArgumentMatchers.any; +import static team.teamby.teambyteam.common.fixtures.acceptance.IcalendarAcceptanceFixtures.GET_ICALENDAR_PUBLISHED_URL; + public class IcalendarAcceptanceTest extends AcceptanceTest { @MockBean @@ -56,6 +57,7 @@ public void afterIcalendarCreation() { public void await() throws InterruptedException { countDownLatch.await(); + Thread.sleep(10); } } } diff --git a/backend/src/test/java/team/teamby/teambyteam/sse/application/SseSubscribeServiceTest.java b/backend/src/test/java/team/teamby/teambyteam/sse/application/SseSubscribeServiceTest.java index eebef8462..0774056b7 100644 --- a/backend/src/test/java/team/teamby/teambyteam/sse/application/SseSubscribeServiceTest.java +++ b/backend/src/test/java/team/teamby/teambyteam/sse/application/SseSubscribeServiceTest.java @@ -15,21 +15,16 @@ import team.teamby.teambyteam.common.fixtures.MemberFixtures; import team.teamby.teambyteam.member.configuration.dto.MemberEmailDto; import team.teamby.teambyteam.member.domain.Member; -import team.teamby.teambyteam.sse.TestEvent; import team.teamby.teambyteam.sse.domain.SseEmitters; import team.teamby.teambyteam.sse.domain.TeamPlaceEmitterId; import team.teamby.teambyteam.sse.domain.TeamPlaceEmitterRepository; -import team.teamby.teambyteam.sse.domain.TeamPlaceEventId; import java.io.IOException; -import java.util.Arrays; -import java.util.List; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; class SseSubscribeServiceTest extends ServiceTest { @@ -79,62 +74,6 @@ void successWithInitialDummyMessage() throws IOException, InterruptedException { }); } - @Test - @DisplayName("SSE 연결 수 저장된 케시 이벤트들을 발행한다.") - void successWithCachedEvent() throws IOException, InterruptedException { - // given - final Member member = testFixtureBuilder.buildMember(MemberFixtures.PHILIP()); - final MemberEmailDto email = new MemberEmailDto(member.getEmailValue()); - final Long teamPlaceId = 1L; - final String eventName = "test"; - final String eventMessage = "message"; - final TestEvent cachedEvent = new TestEvent(teamPlaceId, eventName, eventMessage); - - final TeamPlaceEventId lastEventId = TeamPlaceEventId.of(teamPlaceId, eventName); - Thread.sleep(100); - final TeamPlaceEventId cachedEventId = TeamPlaceEventId.of(teamPlaceId, eventName); - - BDDMockito.given(teamPlaceEmitterRepository.findAllEventCacheWithId(teamPlaceId)) - .willReturn(Map.of(cachedEventId, cachedEvent.getEvent())); - - // when - - final SseEmitter sseEmitter = sseSubscribeService.subscribe(teamPlaceId, email, lastEventId.toString()); - - Thread.sleep(1000); - // then - // verify if the SSE is sent - ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(SseEmitter.SseEventBuilder.class); - verify(sseEmitter, times(2)).send(argumentCaptor.capture()); - - // verify the content of the SSE - final List allEvents = argumentCaptor.getAllValues(); - - final Set dummyEventProperties = allEvents.get(0).build(); - final String dummySse = SsePropertyToString(dummyEventProperties); - - final Set cachedEventProperties = allEvents.get(1).build(); - final String cachedSse = SsePropertyToString(cachedEventProperties); - - final String[] dummyResult = dummySse.split("\n"); - final String[] cachedResult = cachedSse.split("\n"); - - Arrays.stream(dummyResult).forEach(System.out::println); - System.out.println(); - Arrays.stream(cachedResult).forEach(System.out::println); - - SoftAssertions.assertSoftly(softly -> { - softly.assertThat(allEvents).hasSize(2); - softly.assertThat(dummyResult[0]).startsWith("id:" + teamPlaceId + "_connect_"); - softly.assertThat(dummyResult[1]).isEqualTo("event:connect"); - softly.assertThat(dummyResult[2]).isEqualTo("data:{\"teamPlaceId\":%d,\"memberId\":%d}", teamPlaceId, member.getId()); - softly.assertThat(cachedResult[0]).startsWith("id:" + teamPlaceId + "_" + eventName + "_"); - softly.assertThat(cachedResult[1]).isEqualTo("event:test"); - softly.assertThat(cachedResult[2]).isEqualTo("data:{\"id\":1,\"data\":\"message\"}"); - }); - - } - private String SsePropertyToString(final Set dummyEventProperties) { final String dummySse = dummyEventProperties.stream() .map(ResponseBodyEmitter.DataWithMediaType::getData) diff --git a/backend/src/test/java/team/teamby/teambyteam/sse/domain/TeamPlaceEmitterRepositoryTest.java b/backend/src/test/java/team/teamby/teambyteam/sse/domain/TeamPlaceEmitterRepositoryTest.java index 9657a547d..19c4d489a 100644 --- a/backend/src/test/java/team/teamby/teambyteam/sse/domain/TeamPlaceEmitterRepositoryTest.java +++ b/backend/src/test/java/team/teamby/teambyteam/sse/domain/TeamPlaceEmitterRepositoryTest.java @@ -1,19 +1,12 @@ package team.teamby.teambyteam.sse.domain; -import org.assertj.core.api.SoftAssertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import org.mockito.Mockito; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; -import java.time.LocalDateTime; -import java.util.Map; - import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.BDDMockito.given; @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.NONE) class TeamPlaceEmitterRepositoryTest { @@ -21,9 +14,6 @@ class TeamPlaceEmitterRepositoryTest { @Autowired private TeamPlaceEmitterRepository teamPlaceEmitterRepository; - @Value("${sse.cache-schedule-period}") - private Long schedulePeriod; - @DisplayName("emitter 저장을 성공한다.") @Test void save() { @@ -38,48 +28,4 @@ void save() { assertThat(save).isEqualTo(sseEmitter); } - @DisplayName("팀플레이스 아이디로 저징된 캐시들을 찾는다.") - @Test - void findCache() { - //given - final TeamPlaceEventId teamPlaceEventId1 = TeamPlaceEventId.of(1L, "test"); - final TeamPlaceEventId teamPlaceEventId2 = TeamPlaceEventId.of(2L, "test"); - final String event1 = "test1"; - final String event2 = "test2"; - - teamPlaceEmitterRepository.addEventCache(teamPlaceEventId1, event1); - teamPlaceEmitterRepository.addEventCache(teamPlaceEventId2, event2); - - //when - final Map events = teamPlaceEmitterRepository.findAllEventCacheWithId(1L); - - //then - SoftAssertions.assertSoftly(softly -> { - softly.assertThat(events).hasSize(1); - softly.assertThat(events.get(teamPlaceEventId1)).isEqualTo(event1); - }); - } - - @DisplayName("이벤트 캐시를 일정 시간마다 제거한다.") - @Test - void refreshCache() throws InterruptedException { - //given - final LocalDateTime pastLocalDateTime = LocalDateTime.now().minusMinutes(1); - - final TeamPlaceEventId teamPlaceEventId = Mockito.spy(TeamPlaceEventId.class); - given(teamPlaceEventId.getTimeStamp()) - .willReturn(pastLocalDateTime); - given(teamPlaceEventId.isPublishedTo(1L)) - .willReturn(true); - - final String event = "event"; - teamPlaceEmitterRepository.addEventCache(teamPlaceEventId, event); - - //when - Thread.sleep(schedulePeriod); - - //then - final Map allEventCacheWithId = teamPlaceEmitterRepository.findAllEventCacheWithId(1L); - assertThat(allEventCacheWithId).hasSize(0); - } } From 5abe6cf67b06e145418abc73d438440b92383340 Mon Sep 17 00:00:00 2001 From: Rulu <79538610+hafnium1923@users.noreply.github.com> Date: Thu, 2 Nov 2023 13:40:34 +0900 Subject: [PATCH 2/6] =?UTF-8?q?[FE]=20=EC=BB=A8=EB=B2=A4=EC=85=98=EC=97=90?= =?UTF-8?q?=20=EB=A7=9E=EA=B2=8C=20=EB=A6=AC=ED=8C=A9=ED=84=B0=EB=A7=81=20?= =?UTF-8?q?(#844)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor: 훅 기본내보내기 제거 * 사용하지않는 import 삭제 * refactor: npm i --- frontend/package-lock.json | 30 +++++++++---------- frontend/src/apis/feed.ts | 2 +- .../common/Menu/MenuList/MenuList.tsx | 2 +- .../my_calendar/MyCalendar/MyCalendar.tsx | 2 +- .../team/TeamExitModal/TeamExitModal.tsx | 2 +- .../ScheduleAddModal/ScheduleAddModal.tsx | 2 +- .../ScheduleEditModal/ScheduleEditModal.tsx | 2 +- .../TeamCalendar/TeamCalendar.tsx | 2 +- .../src/hooks/schedule/useScheduleAddModal.ts | 4 +-- .../hooks/schedule/useScheduleEditModal.ts | 4 +-- frontend/src/hooks/team/useTeamExitModal.ts | 4 +-- .../src/hooks/team/useTeamPlaceInfoModal.ts | 2 +- frontend/src/hooks/thread/useImageUpload.tsx | 4 +-- frontend/src/hooks/thread/useTeamFeedPage.ts | 2 +- frontend/src/hooks/useBottomSheet.ts | 4 +-- frontend/src/hooks/useCalendar.ts | 4 +-- frontend/src/hooks/useClickOutside.ts | 4 +-- frontend/src/hooks/user/useUserInfoModal.ts | 2 +- 18 files changed, 32 insertions(+), 46 deletions(-) diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 5056bbb58..89347a04b 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -6117,21 +6117,6 @@ "url": "https://github.com/sponsors/gregberge" } }, - "node_modules/@tanstack/match-sorter-utils": { - "version": "8.8.4", - "resolved": "https://registry.npmjs.org/@tanstack/match-sorter-utils/-/match-sorter-utils-8.8.4.tgz", - "integrity": "sha512-rKH8LjZiszWEvmi01NR72QWZ8m4xmXre0OOwlRGnjU01Eqz/QnN+cqpty2PJ0efHblq09+KilvyR7lsbzmXVEw==", - "dependencies": { - "remove-accents": "0.4.2" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/kentcdodds" - } - }, "node_modules/@swc/core": { "version": "1.3.95", "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.3.95.tgz", @@ -6358,6 +6343,21 @@ "integrity": "sha512-myfUej5naTBWnqOCc/MdVOLVjXUXtIA+NpDrDBKJtLLg2shUjBu3cZmB/85RyitKc55+lUUyl7oRfLOvkr2hsw==", "dev": true }, + "node_modules/@tanstack/match-sorter-utils": { + "version": "8.8.4", + "resolved": "https://registry.npmjs.org/@tanstack/match-sorter-utils/-/match-sorter-utils-8.8.4.tgz", + "integrity": "sha512-rKH8LjZiszWEvmi01NR72QWZ8m4xmXre0OOwlRGnjU01Eqz/QnN+cqpty2PJ0efHblq09+KilvyR7lsbzmXVEw==", + "dependencies": { + "remove-accents": "0.4.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kentcdodds" + } + }, "node_modules/@tanstack/query-core": { "version": "4.36.1", "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-4.36.1.tgz", diff --git a/frontend/src/apis/feed.ts b/frontend/src/apis/feed.ts index d66cd7013..192cffd5b 100644 --- a/frontend/src/apis/feed.ts +++ b/frontend/src/apis/feed.ts @@ -1,6 +1,6 @@ import { http } from '~/apis/http'; import { THREAD_SIZE } from '~/constants/feed'; -import type { Thread, NoticeThread, ThreadContent } from '~/types/feed'; +import type { Thread, NoticeThread } from '~/types/feed'; interface ThreadsResponse { threads: Thread[]; diff --git a/frontend/src/components/common/Menu/MenuList/MenuList.tsx b/frontend/src/components/common/Menu/MenuList/MenuList.tsx index a15656890..a543087f9 100644 --- a/frontend/src/components/common/Menu/MenuList/MenuList.tsx +++ b/frontend/src/components/common/Menu/MenuList/MenuList.tsx @@ -1,7 +1,7 @@ import { useRef, useEffect } from 'react'; import type { MouseEventHandler, PropsWithChildren } from 'react'; import { useMenu } from '~/hooks/useMenu'; -import useClickOutside from '~/hooks/useClickOutside'; +import { useClickOutside } from '~/hooks/useClickOutside'; import { useListKeyboardNavigation } from '~/hooks/useListKeyboardNavigation'; import * as S from './MenuList.styled'; diff --git a/frontend/src/components/my_calendar/MyCalendar/MyCalendar.tsx b/frontend/src/components/my_calendar/MyCalendar/MyCalendar.tsx index 101365ae4..3060d8ada 100644 --- a/frontend/src/components/my_calendar/MyCalendar/MyCalendar.tsx +++ b/frontend/src/components/my_calendar/MyCalendar/MyCalendar.tsx @@ -1,7 +1,7 @@ import { Fragment } from 'react'; import Button from '~/components/common/Button/Button'; import Text from '~/components/common/Text/Text'; -import useCalendar from '~/hooks/useCalendar'; +import { useCalendar } from '~/hooks/useCalendar'; import * as S from './MyCalendar.styled'; import DateCell from '~/components/common/DateCell/DateCell'; import { ArrowLeftIcon, ArrowRightIcon } from '~/assets/svg'; diff --git a/frontend/src/components/team/TeamExitModal/TeamExitModal.tsx b/frontend/src/components/team/TeamExitModal/TeamExitModal.tsx index 76363a842..222910d0d 100644 --- a/frontend/src/components/team/TeamExitModal/TeamExitModal.tsx +++ b/frontend/src/components/team/TeamExitModal/TeamExitModal.tsx @@ -2,7 +2,7 @@ import Button from '~/components/common/Button/Button'; import Modal from '~/components/common/Modal/Modal'; import Text from '~/components/common/Text/Text'; import Input from '~/components/common/Input/Input'; -import useTeamExitModal from '~/hooks/team/useTeamExitModal'; +import { useTeamExitModal } from '~/hooks/team/useTeamExitModal'; import { CloseIcon, LogoutIcon } from '~/assets/svg'; import * as S from './TeamExitModal.styled'; import { useModal } from '~/hooks/useModal'; diff --git a/frontend/src/components/team_calendar/ScheduleAddModal/ScheduleAddModal.tsx b/frontend/src/components/team_calendar/ScheduleAddModal/ScheduleAddModal.tsx index 6320fe0ba..70b46b696 100644 --- a/frontend/src/components/team_calendar/ScheduleAddModal/ScheduleAddModal.tsx +++ b/frontend/src/components/team_calendar/ScheduleAddModal/ScheduleAddModal.tsx @@ -5,7 +5,7 @@ import Modal from '~/components/common/Modal/Modal'; import Text from '../../common/Text/Text'; import Button from '../../common/Button/Button'; import Input from '../../common/Input/Input'; -import useScheduleAddModal from '~/hooks/schedule/useScheduleAddModal'; +import { useScheduleAddModal } from '~/hooks/schedule/useScheduleAddModal'; import Checkbox from '~/components/common/Checkbox/Checkbox'; import TeamBadge from '~/components/team/TeamBadge/TeamBadge'; import TimeTableMenu from '~/components/team_calendar/TimeTableMenu/TimeTableMenu'; diff --git a/frontend/src/components/team_calendar/ScheduleEditModal/ScheduleEditModal.tsx b/frontend/src/components/team_calendar/ScheduleEditModal/ScheduleEditModal.tsx index 6836caad5..85c627ff5 100644 --- a/frontend/src/components/team_calendar/ScheduleEditModal/ScheduleEditModal.tsx +++ b/frontend/src/components/team_calendar/ScheduleEditModal/ScheduleEditModal.tsx @@ -5,7 +5,7 @@ import Button from '~/components/common/Button/Button'; import { CloseIcon } from '~/assets/svg'; import Input from '~/components/common/Input/Input'; import Text from '~/components/common/Text/Text'; -import useScheduleEditModal from '~/hooks/schedule/useScheduleEditModal'; +import { useScheduleEditModal } from '~/hooks/schedule/useScheduleEditModal'; import type { Schedule } from '~/types/schedule'; import TeamBadge from '~/components/team/TeamBadge/TeamBadge'; import TimeTableMenu from '~/components/team_calendar/TimeTableMenu/TimeTableMenu'; diff --git a/frontend/src/components/team_calendar/TeamCalendar/TeamCalendar.tsx b/frontend/src/components/team_calendar/TeamCalendar/TeamCalendar.tsx index c13c02654..e0dab2737 100644 --- a/frontend/src/components/team_calendar/TeamCalendar/TeamCalendar.tsx +++ b/frontend/src/components/team_calendar/TeamCalendar/TeamCalendar.tsx @@ -9,7 +9,7 @@ import ScheduleEditModal from '~/components/team_calendar/ScheduleEditModal/Sche import ScheduleMoreCell from '~/components/team_calendar/ScheduleMoreCell/ScheduleMoreCell'; import DailyScheduleModal from '~/components/team_calendar/DailyScheduleModal/DailyScheduleModal'; import ICalendarModal from '~/components/team_calendar/ICalendarModal/ICalendarModal'; -import useCalendar from '~/hooks/useCalendar'; +import { useCalendar } from '~/hooks/useCalendar'; import { useScheduleModal } from '~/hooks/schedule/useScheduleModal'; import { useFetchSchedules } from '~/hooks/queries/useFetchSchedules'; import { useModal } from '~/hooks/useModal'; diff --git a/frontend/src/hooks/schedule/useScheduleAddModal.ts b/frontend/src/hooks/schedule/useScheduleAddModal.ts index cca891275..389801f16 100644 --- a/frontend/src/hooks/schedule/useScheduleAddModal.ts +++ b/frontend/src/hooks/schedule/useScheduleAddModal.ts @@ -7,7 +7,7 @@ import type { ChangeEventHandler, FormEventHandler } from 'react'; import { useToast } from '~/hooks/useToast'; import { useTeamPlace } from '~/hooks/useTeamPlace'; -const useScheduleAddModal = (clickedDate: Date) => { +export const useScheduleAddModal = (clickedDate: Date) => { const { year, month, date } = parseDate(clickedDate); const dateString = `${year}-${String(month + 1).padStart(2, '0')}-${String( date, @@ -145,5 +145,3 @@ const useScheduleAddModal = (clickedDate: Date) => { }, }; }; - -export default useScheduleAddModal; diff --git a/frontend/src/hooks/schedule/useScheduleEditModal.ts b/frontend/src/hooks/schedule/useScheduleEditModal.ts index a9ed33b18..6213c231d 100644 --- a/frontend/src/hooks/schedule/useScheduleEditModal.ts +++ b/frontend/src/hooks/schedule/useScheduleEditModal.ts @@ -7,7 +7,7 @@ import type { Schedule } from '~/types/schedule'; import { useToast } from '~/hooks/useToast'; import { useTeamPlace } from '~/hooks/useTeamPlace'; -const useScheduleEditModal = ( +export const useScheduleEditModal = ( scheduleId: Schedule['id'], initialSchedule?: Schedule, ) => { @@ -147,5 +147,3 @@ const useScheduleEditModal = ( }, }; }; - -export default useScheduleEditModal; diff --git a/frontend/src/hooks/team/useTeamExitModal.ts b/frontend/src/hooks/team/useTeamExitModal.ts index d97bc247b..0f2677ced 100644 --- a/frontend/src/hooks/team/useTeamExitModal.ts +++ b/frontend/src/hooks/team/useTeamExitModal.ts @@ -7,7 +7,7 @@ import { useModal } from '~/hooks/useModal'; import { useTeamPlace } from '~/hooks/useTeamPlace'; import { useToast } from '~/hooks/useToast'; -const useTeamExitModal = (onClose: () => void) => { +export const useTeamExitModal = (onClose: () => void) => { const navigate = useNavigate(); const { teamPlaces, teamPlaceId, displayName, resetTeamPlace } = useTeamPlace(); @@ -72,5 +72,3 @@ const useTeamExitModal = (onClose: () => void) => { }, }; }; - -export default useTeamExitModal; diff --git a/frontend/src/hooks/team/useTeamPlaceInfoModal.ts b/frontend/src/hooks/team/useTeamPlaceInfoModal.ts index 8aebd6f2a..69a2c72ab 100644 --- a/frontend/src/hooks/team/useTeamPlaceInfoModal.ts +++ b/frontend/src/hooks/team/useTeamPlaceInfoModal.ts @@ -4,7 +4,7 @@ import { MAX_USER_NAME_LENGTH } from '~/constants/user'; import { useFetchTeamPlaceInviteCode } from '~/hooks/queries/useFetchTeamPlaceInviteCode'; import { useFetchTeamPlaceMembers } from '~/hooks/queries/useFetchTeamPlaceMembers'; import { useModifyMyTeamPlaceUserInfo } from '~/hooks/queries/useModifyMyTeamPlaceUserInfo'; -import useClickOutside from '~/hooks/useClickOutside'; +import { useClickOutside } from '~/hooks/useClickOutside'; import { useTeamPlace } from '~/hooks/useTeamPlace'; import { useToast } from '~/hooks/useToast'; import type { UserInfo } from '~/types/team'; diff --git a/frontend/src/hooks/thread/useImageUpload.tsx b/frontend/src/hooks/thread/useImageUpload.tsx index 7cb4904d2..d9e57f3aa 100644 --- a/frontend/src/hooks/thread/useImageUpload.tsx +++ b/frontend/src/hooks/thread/useImageUpload.tsx @@ -18,7 +18,7 @@ import { generateUuid } from '~/utils/generateUuid'; * @returns {deleteImageByUuid} deleteImageByUuid - `uuid`를 이용해 특정 이미지를 삭제할 시 호출하는 함수입니다. * @returns {deleteAllImages} deleteAllImages - 모든 이미지를 삭제할 때 호출하는 함수입니다. */ -const useImageUpload = () => { +export const useImageUpload = () => { const [filesWithUuid, setFilesWithUuid] = useState([]); const [previewImages, setPreviewImages] = useState([]); const { showToast } = useToast(); @@ -130,5 +130,3 @@ const useImageUpload = () => { deleteAllImages, }; }; - -export default useImageUpload; diff --git a/frontend/src/hooks/thread/useTeamFeedPage.ts b/frontend/src/hooks/thread/useTeamFeedPage.ts index cce2a42c3..376beda8a 100644 --- a/frontend/src/hooks/thread/useTeamFeedPage.ts +++ b/frontend/src/hooks/thread/useTeamFeedPage.ts @@ -7,7 +7,7 @@ import { useEffect, useRef, useState } from 'react'; import { useFetchNoticeThread } from '~/hooks/queries/useFetchNoticeThread'; import { useSendNoticeThread } from '~/hooks/queries/useSendNoticeThread'; import { useSendThread } from '~/hooks/queries/useSendThread'; -import useImageUpload from '~/hooks/thread/useImageUpload'; +import { useImageUpload } from '~/hooks/thread/useImageUpload'; import { useTeamPlace } from '~/hooks/useTeamPlace'; import { useToast } from '~/hooks/useToast'; diff --git a/frontend/src/hooks/useBottomSheet.ts b/frontend/src/hooks/useBottomSheet.ts index 4da0854fd..67a8c10d2 100644 --- a/frontend/src/hooks/useBottomSheet.ts +++ b/frontend/src/hooks/useBottomSheet.ts @@ -2,7 +2,7 @@ import { useRef, useState } from 'react'; import { useModal } from '~/hooks/useModal'; import theme from '~/styles/theme'; -const useBottomSheet = () => { +export const useBottomSheet = () => { const [isClosing, setIsClosing] = useState(false); const timerId = useRef>(); const { closeModal } = useModal(); @@ -25,5 +25,3 @@ const useBottomSheet = () => { isClosing, }; }; - -export default useBottomSheet; diff --git a/frontend/src/hooks/useCalendar.ts b/frontend/src/hooks/useCalendar.ts index dc63a04c0..9613238f0 100644 --- a/frontend/src/hooks/useCalendar.ts +++ b/frontend/src/hooks/useCalendar.ts @@ -3,7 +3,7 @@ import { CALENDAR } from '~/constants/calendar'; import { arrayOf } from '~/utils/arrayOf'; import { parseDate } from '~/utils/parseDate'; -const useCalendar = () => { +export const useCalendar = () => { const [currentDate, setCurrentDate] = useState(new Date()); const { year, month, date } = parseDate(currentDate); const { day: startDayOfMonth } = parseDate(new Date(year, month, 1)); @@ -41,5 +41,3 @@ const useCalendar = () => { }, }; }; - -export default useCalendar; diff --git a/frontend/src/hooks/useClickOutside.ts b/frontend/src/hooks/useClickOutside.ts index e3caf9daf..d1b93fecf 100644 --- a/frontend/src/hooks/useClickOutside.ts +++ b/frontend/src/hooks/useClickOutside.ts @@ -1,7 +1,7 @@ import { useEffect } from 'react'; import type { RefObject, MutableRefObject } from 'react'; -const useClickOutside = ( +export const useClickOutside = ( ref: RefObject | MutableRefObject, callback: EventListener, ) => { @@ -21,5 +21,3 @@ const useClickOutside = ( }; }, [callback, ref]); }; - -export default useClickOutside; diff --git a/frontend/src/hooks/user/useUserInfoModal.ts b/frontend/src/hooks/user/useUserInfoModal.ts index dd4ccfb3d..1e77bf56c 100644 --- a/frontend/src/hooks/user/useUserInfoModal.ts +++ b/frontend/src/hooks/user/useUserInfoModal.ts @@ -3,7 +3,7 @@ import type { ChangeEventHandler, FormEventHandler } from 'react'; import { useNavigate } from 'react-router-dom'; import { useFetchUserInfo } from '~/hooks/queries/useFetchUserInfo'; import { useModifyUserInfo } from '~/hooks/queries/useModifyUserInfo'; -import useClickOutside from '~/hooks/useClickOutside'; +import { useClickOutside } from '~/hooks/useClickOutside'; import { useModal } from '~/hooks/useModal'; import { useToast } from '~/hooks/useToast'; import { LOCAL_STORAGE_KEY } from '~/constants/localStorage'; From 04c5ae014bffd7882c8520d34a97e45c4ace64de Mon Sep 17 00:00:00 2001 From: Suyoung Date: Thu, 2 Nov 2023 16:45:53 +0900 Subject: [PATCH 3/6] =?UTF-8?q?[FE]=20=ED=8C=80=20=EC=A0=95=EB=B3=B4=20?= =?UTF-8?q?=EC=95=84=EC=9D=B4=EC=BD=98=20=EC=A0=91=EA=B7=BC=20=EC=8B=9C=20?= =?UTF-8?q?prefetching=20=EC=A0=81=EC=9A=A9=20(#833)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 팀 정보 아이콘 hover시 prefetching 적용 * feat: focus 시에도 prefetching 하도록 적용 --- .../src/components/common/Header/Header.tsx | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/frontend/src/components/common/Header/Header.tsx b/frontend/src/components/common/Header/Header.tsx index 0ff91d56d..be2703ce8 100644 --- a/frontend/src/components/common/Header/Header.tsx +++ b/frontend/src/components/common/Header/Header.tsx @@ -1,5 +1,6 @@ import { useCallback, useEffect, useState } from 'react'; import { Link, useNavigate } from 'react-router-dom'; +import { useQueryClient } from '@tanstack/react-query'; import { EditIcon, LogoIcon, TeamIcon } from '~/assets/svg'; import * as S from './Header.styled'; import TeamBadge from '~/components/team/TeamBadge/TeamBadge'; @@ -15,6 +16,8 @@ import UserInfoModal from '~/components/user/UserInfoModal/UserInfoModal'; import TeamColorEditModal from '~/components/team/TeamColorEditModal/TeamColorEditModal'; import AccountDeleteModal from '~/components/user/AccountDeleteModal/AccountDeleteModal'; import ServiceCenterModal from '~/components/user/ServiceCenterModal/ServiceCenterModal'; +import { fetchTeamPlaceInviteCode, fetchTeamPlaceMembers } from '~/apis/team'; +import { STALE_TIME } from '~/constants/query'; export type HeaderModalType = | 'team' @@ -33,12 +36,34 @@ const Header = () => { } = useTeamPlace(); const navigate = useNavigate(); const { openModal, isModalOpen } = useModal(); + const queryClient = useQueryClient(); const { userInfo } = useFetchUserInfo(); const [teamName, setTeamName] = useState(displayName ?? ''); const [modalOpenType, setModalOpenType] = useState(); + const prefetchTeamPlaceInfo = async () => { + if (!teamPlaceId) { + return; + } + + await queryClient.prefetchQuery( + ['teamPlaceMembers', teamPlaceId], + () => fetchTeamPlaceMembers(teamPlaceId), + { + staleTime: STALE_TIME.TEAM_PLACE_MEMBERS, + }, + ); + await queryClient.prefetchQuery( + ['teamPlaceInviteCode', teamPlaceId], + () => fetchTeamPlaceInviteCode(teamPlaceId), + { + staleTime: STALE_TIME.TEAM_PLACE_INVITE_CODE, + }, + ); + }; + const handleTeamNameChange = useCallback( (value: string) => { if (value === '') { @@ -134,6 +159,8 @@ const Header = () => {