회고

[개발일지] - 115

Happy Programmer 2023. 10. 23. 23:04

일정을 정리하고 난 다음 배치 작성을 시작했는데

다들 웅성거리며 뭘 찾고 있길래 물어보니 명패 하나가 안보인다고 했다.

 

창고정리하면서 본적은 없지만 창고도 확인하려고 하시는 것 같아서

창고에 있는 박스를 전부 내려서 다 확인했지만 찾을 수 없었다.

 

배치 작성으로 돌아와서 진행하는데

왜 배치로 만드는걸 꺼렸는지 이해가 되지 않을 정도로 순조롭게 진행되었고

아래와 같은 형태로 간단하게 배치를 작성할 수 있었다.

(Warpper, 처리 작업 별도 수행 필요)

Global without sharing class BatchName implements Database.Batchable<Object>,Database.AllowsCallouts {

    Public List<Object> start(Database.BatchableContext BC) {
        List<ObjName> obj= [SELECT Id
                                   , Name
                                FROM ObjName];
				return obj
		}

    Public void execute(Database.BatchableContext BC, List<Object> scope) {
    
        HttpRequest httpReq = new HttpRequest();
        HttpResponse httpRes = new HttpResponse();
        Http http = new Http();
        InputWrapper body = new InputWrapper();
        for(Object obj: scope){
						//개체 형변환 다시 해주는 부분
            ObjName anyName = (ObjName)obj;
        }
        String resBody = JSON.serialize(body);

        httpReq.setMethod('POST');
        httpReq.setBody(resBody);
        httpReq.setTimeout(120000);
        httpReq.setHeader('Authorization', 'Bearer ' + 'key');
        httpReq.setHeader('Content-Type', 'application/json');
        httpReq.setHeader('Accept', 'application/json');
        httpRes = http.send(httpReq);
        System.debug(httpRes);
        System.debug(httpRes.getBody());
    }

    Public void finish(Database.BatchableContext BC) {

    }

 

스케줄러에도 배치 호출과 사이즈만 진행하면 되었고

스케줄러 호출 또한 저번에 넣었던 runSchedule을 작동시키면 끝났기 때문에 간편했다.

Database.executeBatch(new BatchName(), BatchSize); // 배치호출

 

스케줄러는 아래와 같이 구성해서 배치 호출을 진행하면 되는데

내 의도에 맞게 기본 틀을 제공해주는 곳이 없었기 때문에

초반에 시간이 오래 걸리고 회피하려고 했던 것 같다.

Global with sharing class SchedulerName implements Schedulable {
    Global static String cron          = '0 27 9 * * ?';
    
		//호출용
    Global static void runSchedule() {
        System.schedule('테스트중', cron, new SchedulerName());
    }

    Global void execute(SchedulableContext sc) {
        SchedulerName.executeBatch(sc.getTriggerId());
    }

    Global static void executeBatch(Id jId){
        Database.executeBatch(new BatchName(), BatchSize);
    }
}

 

순서대로면 트리거를 작성해야 하는데

파일정보 저장 트리거를 작성해야 한다고 적고

저장에 꽃혀서 파일 저장 부분인 DocumentLink를 먼저 확인해버렸다.

 

일단 api가 없고 정의서도 없기 때문에 저장될 파일에 대한 정보가 없었는데

ContentVersion, Document와 비교하다가 Version으로 하려고 했는데

DocumentLink 자체가 DocumentId를 넣어야 하기 때문에 Document를 기준으로 하기로 변경했다.

 

처음에는 ContentVersion에 어떤 키가 입력되는지를 확인해야 한다고 생각했지만

일단 내가 작성하려는 개체와 동일한지만 확인할 수 있으면

Target이 된 개체에 대한 정보를 파일정보에서 찾을 수 있기 때문에

DocumentLink의 필드를 확인했다.

 

생각보다 DocumentLink는 별거 없었는데

생성할 때 필요한 조건은 ContentDocumentId, LinkedEntityId 두개 뿐이었고

아래와 같이 간단히 생성 가능했다.(Id는 아무거나 넣은 값)

ContentDocumentLink cdl = new ContentDocumentLink();
cdl.ContentDocumentId = '0911y000001YgKaKAK';
cdl.LinkedEntityId = '17k1y000000A3ajACD';
insert cdl;

 

결국 Document의 OwnerId가 뭔지만 알면 생성 필수 조건을 다 알 수 있는데

예전에도 개체 타입을 알 수 있었던 것 같아서 검색해보니

Id.getSObjectType()를 통해 개체명을 알 수 있었고

Id.getSObjectType() == TargetObjName.SObjectType을 통해 조건문을 걸 수 있었다.

 

DocumentLink 부분에 대해 작업을 하던 도중

잘못된 값 입력이 없을 것이라고 생각하고(입력한 값이 들어오는 자동화 시스템이니까)

Try/Catch, If_log 등은 추후 보강하려고 우선순위를 미뤄뒀는데

상대방 입장에서 테스트를 하는데 에러만 터지니 사유라도 보내달라는 요청이 있어서

Try/Catch를 사용해 해당 부분의 에러가 발생할 경우

에러코드, 에러메세지(Exception), 에러여부를 반환해줬다.

 

바로 뒤에 주간회의가 잡혀있었기 때문에

회의와 관련된 내용을 잠깐 보다가 바로 회의에 들어갔고

회의 내용 대다수는 페이지 관련 작업을 하시는 동기분에게 넘어갔기 때문에 내가 할 작업은 없었지만

커스텀 스피너 사용 부분이 흥미로워보여서 한번 시도해보기로 했다.

 

회의중에는 세일즈포스 기본기능이기 때문에 불가능할 가능성이 높다고 넘어가는 분위기였기 떄문에

될 것 같다고 하기 그래서 일단 혼자 시도해봤는데 생각보다 많이 어려웠다.

 

스피너 관련 확인 도중 첫번쨰 프로젝트에서 요청사항이 날아와서

해당 부분에 대해 확인했는데 다행히 다른 동기분(앞의 동기분 아님)이 처리해주셔서

다시 스피너로 돌아올 수 있었다.

 

스피너를 바꾸기 위해 초반에는 해당 파일을 바꿔치기 하는 방식을 생각했는데

Inspector로 해당 파일을 찾아봤지만 ContentVersion, ContentDocumnet등에도 존재하지 않았고

직접 개발자도구로 찍어봐도 조그만 점 하나를 6개 모아둔 것 같은 이상한 작동방식이었다.

 

이미지 파일을 개발자도구 내에서 이것저것 눌러봤지만

의미모를 프로필 사진들만 자꾸 나와서 다른 방식을 찾아야 했다.

.slds-avatar.slds-avatar_profile-image-medium {background: var(--lwc-userDefaultAvatarMedium,url(/_slds/images/profile_avatar_160.png cache=bfba2c9a)) top left/cover no-repeat}

 

결국 커뮤니티 Builder -> Theme -> Edit Css로 이동한 다음 수정할 수 있었고

siteforceLoadingBalls를 hidden처리하고 원하는 이미지를 덮어씌울 수 있었지만

알고보니 이건 스탠다드 컴포넌트의 스피너를 제어하는 것이 아니라 사이트의 스피너만 제어하는 것이었다.

 

사이트 내부에 여러가지 기본제공 컴포넌트를 사용하는데

해당 부분에 통일되지 않은 스피너들이 있으면 오히려 이상해보일 것 같았기 때문에

이번에는 컴포넌트를 차례대로 찾아보기 시작했다.

 

Page, Vf page, Aura, LWC, Conponents 등 이것저것 확인했지만

Record List 등 기본적으로 제공되는 컴포넌트명은 하나도 보이지 않았고

기본 컴포넌트 코드 수정하는 방법 또한 찾을 수 없었다.

 

물론 기본 컴포넌트를 오버라이드 하는 방식 하나는 찾아볼 수 있었지만

여기에서만 사용하는 것이 아니고

사용하는 모든 컴포넌트에 오버라이드를 해줄 수도 없을 것 같아서 다시 다른 방식을 찾아봤다.

 

최종적으로 찾은 해답은 커뮤니티 페이지에 css 덮어씌우기가 맞았는데

기본의 스피너가 balls였다면 standard component의 스피너는 slds-spinner_container로 잡아야 했다.

 

해당 부분을 찾기 위해 개발자도구로 열심히 찾았지만

순식간에 사라져버려서 해당 css등 확인이 불가능했었는데

다행히 해당 태그 상단의 클래스명 중 하나가 slds-hide인 것을 발견할 수 있었고

해당 slds-hide 클래스를 지우니 스피너가 계속 남아있어 자세히 코드를 확인할 수 있었다.

.siteforceLoadingBalls.global {
    left: 0% !important;
}
.siteforceLoadingBalls {
    visibility: hidden !important;
}
.siteforceLoadingBalls:after {
    visibility: visible !important;
    content: url("https://cdn.aitimes.com/news/photo/202110/141182_143169_277.gif") ;
    position: absolute;
    left: 40vw;
    top: -10vh
}


.slds-spinner_container{
    visibility: hidden;
}

.slds-spinner_container:after {
    visibility: visible;
    content: url("https://cdn.aitimes.com/news/photo/202110/141182_143169_277.gif") ;
    position: absolute;
    left: 35%;
}

 

위 코드 중 위에 있는 것은 페이지 자체 로딩이 걸릴 떄 진행되는 스피너이고

아래 코드는 sfdc 기본 스피너 자체에 적용되는 css override로

명칭만 봐도 lightning 관련 클래스 느낌이 가득하다.

 

ContentDocumentLink를 작성하려고 했지만

어떤 코드가 날아올지 몰라서 작성해도 수정 가능성이 높고

해당 코드가 날아오기 전 내가 발송해야 하는 엔드포인트도 없는 상황이라

이거보다 저거보다 시간만 낭비한 느낌이 좀 들었다.

 

원래 스피너가 없었으면 사례까지 다 작성할 수 있었겠지만

시간이 부족해서 Link부분을 중단하고 사례 정리를 시작했다.

 

 

(1).백준 17598번 Animal King Election은 Lion과 Tiger의 투표를 할 경우

누가 동물의 왕이 되는지를 묻는 문제였다.

 

단순하게 각 명칭이 호출될 때마다 수치를 증가시킨 다음

최종적으로 비교 후 큰 값을 출력했다.

const input = `Lion
Lion
Tiger
Tiger
Lion
Lion
Tiger
Tiger
Tiger`.split('\n')

let Tiger = 0
let Lion = 0

for(let i = 0 ; i < input.length ; i++){
    if(input[i] == 'Tiger'){
        Tiger++
    }
    else{
        Lion++
    }
}

if(Tiger > Lion){
    console.log('Tiger')
}
else{
    console.log('Lion')
}