mongodb 인덱스 지정
잔존율 조회
This commit is contained in:
@@ -0,0 +1,178 @@
|
||||
package com.caliverse.admin.Indicators.Indicatorsservice.aggregationservice;
|
||||
|
||||
import com.caliverse.admin.Indicators.Indicatordomain.IndicatorsLog;
|
||||
import com.caliverse.admin.Indicators.Indicatorsservice.base.IndicatorsLogLoadServiceBase;
|
||||
import com.caliverse.admin.global.common.constants.AdminConstants;
|
||||
import org.bson.Document;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.mongodb.core.MongoTemplate;
|
||||
import org.springframework.data.mongodb.core.aggregation.*;
|
||||
import org.springframework.data.mongodb.core.query.Criteria;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
public class IndicatorsRetentionLoadService extends IndicatorsLogLoadServiceBase {
|
||||
public IndicatorsRetentionLoadService(
|
||||
@Qualifier("mongoIndicatorTemplate") MongoTemplate mongoTemplate
|
||||
) {
|
||||
super(mongoTemplate);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends IndicatorsLog> List<T> getIndicatorsLogData(String startTime, String endTime, Class<T> clazz) {
|
||||
LookupOperation lookupOperation = LookupOperation.newLookup()
|
||||
.from("userCreate")
|
||||
.localField("userGuid")
|
||||
.foreignField("userGuid")
|
||||
.as("userInfo");
|
||||
|
||||
UnwindOperation unwindOperation = Aggregation.unwind("userInfo");
|
||||
|
||||
Criteria criteria = makeCriteria(startTime, endTime, "userInfo.logDay");
|
||||
|
||||
AddFieldsOperation addFieldsOp = Aggregation.addFields()
|
||||
.addField("createDate")
|
||||
.withValue(DateOperators.dateFromString("$userInfo.logDay"))
|
||||
.addField("loginDate")
|
||||
.withValue(DateOperators.dateFromString("$logDay"))
|
||||
.build();
|
||||
|
||||
AggregationOperation addFieldsOp2 = context -> new Document("$addFields",
|
||||
new Document("daysSinceCreate",
|
||||
new Document("$dateDiff", new Document()
|
||||
.append("startDate", "$createDate")
|
||||
.append("endDate", "$loginDate")
|
||||
.append("unit", "day")
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
MatchOperation matchOp = Aggregation.match(
|
||||
Criteria.where("daysSinceCreate").gte(1).lte(30)
|
||||
);
|
||||
|
||||
AggregationOperation groupOp = context -> new Document("$group",
|
||||
new Document("_id", new Document("cohortDate", "$userInfo.logDay")
|
||||
.append("userGuid", "$userGuid"))
|
||||
.append("loginDays", new Document("$addToSet", "$daysSinceCreate"))
|
||||
);
|
||||
|
||||
AggregationOperation groupOp2 = context -> new Document("$group",
|
||||
new Document("_id", "$_id.cohortDate")
|
||||
.append("totalUsers", new Document("$sum", 1))
|
||||
.append("d1_retained", new Document("$sum",
|
||||
new Document("$cond", List.of(
|
||||
new Document("$in", List.of(1, "$loginDays")),
|
||||
1,
|
||||
0
|
||||
))
|
||||
))
|
||||
.append("d7_retention_users", new Document("$sum",
|
||||
new Document("$cond", List.of(
|
||||
new Document("$gt", List.of(
|
||||
new Document("$size", new Document("$filter", new Document()
|
||||
.append("input", "$loginDays")
|
||||
.append("cond", new Document("$and", List.of(
|
||||
new Document("$gte", List.of("$$this", 1)),
|
||||
new Document("$lte", List.of("$$this", 7)))
|
||||
))
|
||||
)),
|
||||
0
|
||||
)),
|
||||
1,
|
||||
0
|
||||
))
|
||||
))
|
||||
.append("d30_retention_users", new Document("$sum",
|
||||
new Document("$cond", List.of(
|
||||
new Document("$gt", List.of(
|
||||
new Document("$size", new Document("$filter", new Document()
|
||||
.append("input", "$loginDays")
|
||||
.append("cond", new Document("$and", List.of(
|
||||
new Document("$gte", List.of("$$this", 1)),
|
||||
new Document("$lte", List.of("$$this", 30)))
|
||||
))
|
||||
)),
|
||||
0
|
||||
)),
|
||||
1,
|
||||
0
|
||||
))
|
||||
))
|
||||
);
|
||||
|
||||
AggregationOperation lookupOp = context -> new Document("$lookup",
|
||||
new Document("from", "userCreate")
|
||||
.append("let", new Document("cohortDate", "$_id"))
|
||||
.append("pipeline", List.of(
|
||||
new Document("$match", new Document("$expr",
|
||||
new Document("$eq", List.of("$logDay", "$$cohortDate"))
|
||||
)),
|
||||
new Document("$count", "totalCreated")
|
||||
))
|
||||
.append("as", "cohortInfo")
|
||||
);
|
||||
|
||||
UnwindOperation unwindOp = Aggregation.unwind("cohortInfo", true);
|
||||
|
||||
AggregationOperation projectOp = context -> new Document("$project", new Document()
|
||||
.append("_id", 0)
|
||||
.append("logDay", "$_id")
|
||||
.append("totalCreate", new Document("$ifNull", List.of("$cohortInfo.totalCreated", 0)))
|
||||
.append("totalActiveUsers", "$totalUsers")
|
||||
.append("d1_users", 1)
|
||||
.append("d7_users", 1)
|
||||
.append("d30_users", 1)
|
||||
.append("d1_rate", new Document("$cond", List.of(
|
||||
new Document("$gt", List.of("$cohortInfo.totalCreated", 0)),
|
||||
new Document("$multiply", List.of(
|
||||
new Document("$divide", List.of("$d1_retained", "$cohortInfo.totalCreated")),
|
||||
100
|
||||
)),
|
||||
0
|
||||
)))
|
||||
.append("d7_rate", new Document("$cond", List.of(
|
||||
new Document("$gt", List.of("$cohortInfo.totalCreated", 0)),
|
||||
new Document("$multiply", List.of(
|
||||
new Document("$divide", List.of("$d7_retention_users", "$cohortInfo.totalCreated")),
|
||||
100
|
||||
)),
|
||||
0
|
||||
)))
|
||||
.append("d30_rate", new Document("$cond", List.of(
|
||||
new Document("$gt", List.of("$cohortInfo.totalCreated", 0)),
|
||||
new Document("$multiply", List.of(
|
||||
new Document("$divide", List.of("$d30_retention_users", "$cohortInfo.totalCreated")),
|
||||
100
|
||||
)),
|
||||
0
|
||||
)))
|
||||
);
|
||||
|
||||
List<AggregationOperation> operations = List.of(
|
||||
lookupOperation,
|
||||
unwindOperation,
|
||||
Aggregation.match(criteria),
|
||||
addFieldsOp,
|
||||
addFieldsOp2,
|
||||
matchOp,
|
||||
groupOp,
|
||||
groupOp2,
|
||||
lookupOp,
|
||||
unwindOp,
|
||||
projectOp,
|
||||
Aggregation.sort(Sort.Direction.ASC, AdminConstants.MONGO_DB_KEY_LOGDAY)
|
||||
);
|
||||
|
||||
Aggregation aggregation = Aggregation.newAggregation(operations);
|
||||
|
||||
return mongoTemplate.aggregate(
|
||||
aggregation.withOptions(AggregationOptions.builder().allowDiskUse(true).build())
|
||||
, AdminConstants.MONGO_DB_COLLECTION_LOGIN
|
||||
, clazz
|
||||
).getMappedResults();
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,7 @@ public abstract class IndicatorsLogLoadServiceBase implements IndicatorsLogLoadS
|
||||
return new Criteria()
|
||||
.andOperator(
|
||||
Criteria.where(dateFieldName).gte(startDate),
|
||||
Criteria.where(dateFieldName).lt(endDate)
|
||||
Criteria.where(dateFieldName).lte(endDate)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -2,15 +2,23 @@ package com.caliverse.admin.Indicators.entity;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.springframework.data.mongodb.core.index.CompoundIndex;
|
||||
import org.springframework.data.mongodb.core.index.CompoundIndexes;
|
||||
import org.springframework.data.mongodb.core.index.Indexed;
|
||||
import org.springframework.data.mongodb.core.mapping.Document;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@Document(collection = "currency")
|
||||
@CompoundIndexes({
|
||||
@CompoundIndex(name = "logDay_userGuid_idx", def = "{'logDay': 1, 'userGuid': 1}")
|
||||
})
|
||||
public class CurrencyItemLogInfo extends LogInfoBase{
|
||||
private String id;
|
||||
@Indexed
|
||||
private String logDay;
|
||||
private String accountId;
|
||||
@Indexed
|
||||
private String userGuid;
|
||||
private String userNickname;
|
||||
private String tranId;
|
||||
|
||||
@@ -3,6 +3,9 @@ package com.caliverse.admin.Indicators.entity;
|
||||
import com.caliverse.admin.logs.Indicatordomain.CurrencyMongoLog;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.springframework.data.mongodb.core.index.CompoundIndex;
|
||||
import org.springframework.data.mongodb.core.index.CompoundIndexes;
|
||||
import org.springframework.data.mongodb.core.index.Indexed;
|
||||
import org.springframework.data.mongodb.core.mapping.Document;
|
||||
|
||||
import java.util.List;
|
||||
@@ -11,9 +14,14 @@ import java.util.Map;
|
||||
@Getter
|
||||
@Setter
|
||||
@Document(collection = "currency")
|
||||
@CompoundIndexes({
|
||||
@CompoundIndex(name = "logDay_userGuid_idx", def = "{'logDay': 1, 'userGuid': 1}")
|
||||
})
|
||||
public class CurrencyLogInfo extends LogInfoBase{
|
||||
@Indexed
|
||||
private String logDay;
|
||||
private String accountId;
|
||||
@Indexed
|
||||
private String userGuid;
|
||||
private String userNickname;
|
||||
private Double sapphireAcquired;
|
||||
|
||||
@@ -3,6 +3,9 @@ package com.caliverse.admin.Indicators.entity;
|
||||
import com.caliverse.admin.logs.Indicatordomain.ItemMongoLog;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.springframework.data.mongodb.core.index.CompoundIndex;
|
||||
import org.springframework.data.mongodb.core.index.CompoundIndexes;
|
||||
import org.springframework.data.mongodb.core.index.Indexed;
|
||||
import org.springframework.data.mongodb.core.mapping.Document;
|
||||
|
||||
import java.util.List;
|
||||
@@ -10,9 +13,14 @@ import java.util.List;
|
||||
@Getter
|
||||
@Setter
|
||||
@Document(collection = "item")
|
||||
@CompoundIndexes({
|
||||
@CompoundIndex(name = "logDay_userGuid_idx", def = "{'logDay': 1, 'userGuid': 1}")
|
||||
})
|
||||
public class ItemLogInfo extends LogInfoBase{
|
||||
@Indexed
|
||||
private String logDay;
|
||||
private String accountId;
|
||||
@Indexed
|
||||
private String userGuid;
|
||||
private String userNickname;
|
||||
private Integer totalItems;
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
package com.caliverse.admin.Indicators.entity;
|
||||
|
||||
import com.caliverse.admin.Indicators.Indicatordomain.IndicatorsLog;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
public class RetentionInfo implements IndicatorsLog {
|
||||
private String logDay;
|
||||
private Integer totalCreate;
|
||||
private Integer totalActiveUsers;
|
||||
private Integer d1_users;
|
||||
private Integer d7_users;
|
||||
private Integer d30_users;
|
||||
private Integer d1_rate;
|
||||
private Integer d7_rate;
|
||||
private Integer d30_rate;
|
||||
|
||||
public RetentionInfo(String logDay,
|
||||
Integer totalCreate,
|
||||
Integer totalActiveUsers,
|
||||
Integer d1_users,
|
||||
Integer d7_users,
|
||||
Integer d30_users,
|
||||
Integer d1_rate,
|
||||
Integer d7_rate,
|
||||
Integer d30_rate
|
||||
) {
|
||||
|
||||
this.logDay = logDay;
|
||||
this.totalCreate = totalCreate;
|
||||
this.totalActiveUsers = totalActiveUsers;
|
||||
this.d1_users = d1_users;
|
||||
this.d7_users = d7_users;
|
||||
this.d30_users = d30_users;
|
||||
this.d1_rate = d1_rate;
|
||||
this.d7_rate = d7_rate;
|
||||
this.d30_rate = d30_rate;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -2,14 +2,22 @@ package com.caliverse.admin.Indicators.entity;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.springframework.data.mongodb.core.index.CompoundIndex;
|
||||
import org.springframework.data.mongodb.core.index.CompoundIndexes;
|
||||
import org.springframework.data.mongodb.core.index.Indexed;
|
||||
import org.springframework.data.mongodb.core.mapping.Document;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@Document(collection = "userCreate")
|
||||
@CompoundIndexes({
|
||||
@CompoundIndex(name = "logDay_userGuid_idx", def = "{'logDay': 1, 'userGuid': 1}")
|
||||
})
|
||||
public class UserCreateLogInfo extends LogInfoBase{
|
||||
@Indexed
|
||||
private String logDay;
|
||||
private String accountId;
|
||||
@Indexed
|
||||
private String userGuid;
|
||||
private String userNickname;
|
||||
private String createdTime;
|
||||
|
||||
@@ -2,6 +2,9 @@ package com.caliverse.admin.Indicators.entity;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.springframework.data.mongodb.core.index.CompoundIndex;
|
||||
import org.springframework.data.mongodb.core.index.CompoundIndexes;
|
||||
import org.springframework.data.mongodb.core.index.Indexed;
|
||||
import org.springframework.data.mongodb.core.mapping.Document;
|
||||
|
||||
import java.util.List;
|
||||
@@ -10,9 +13,15 @@ import java.util.Map;
|
||||
@Getter
|
||||
@Setter
|
||||
@Document(collection = "userLogin")
|
||||
@CompoundIndexes({
|
||||
@CompoundIndex(name = "logDay_userGuid_idx", def = "{'logDay': 1, 'userGuid': 1}")
|
||||
})
|
||||
|
||||
public class UserLoginLogInfo extends LogInfoBase{
|
||||
@Indexed
|
||||
private String logDay;
|
||||
private String accountId;
|
||||
@Indexed
|
||||
private String userGuid;
|
||||
private String userNickname;
|
||||
private List<Map<String, Object>> sessions;
|
||||
|
||||
@@ -121,9 +121,11 @@ public class IndicatorsResponse {
|
||||
@Data
|
||||
@Builder
|
||||
public static class Retention{
|
||||
private LocalDate date;
|
||||
@JsonProperty("d-day")
|
||||
private List<Dday> dDay;
|
||||
private String logDay;
|
||||
private Integer totalCreated;
|
||||
private Integer d1_rate;
|
||||
private Integer d7_rate;
|
||||
private Integer d30_rate;
|
||||
}
|
||||
@Data
|
||||
@Builder
|
||||
|
||||
@@ -8,6 +8,8 @@ import java.util.stream.Collectors;
|
||||
|
||||
import com.caliverse.admin.Indicators.Indicatorsservice.aggregationservice.*;
|
||||
import com.caliverse.admin.Indicators.entity.*;
|
||||
import com.caliverse.admin.domain.response.LogResponse;
|
||||
import com.caliverse.admin.global.common.utils.DateUtils;
|
||||
import com.caliverse.admin.logs.logservice.indicators.*;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
@@ -46,6 +48,7 @@ public class IndicatorsService {
|
||||
private final IndicatorsWauLoadService indicatorsWauLoadService;
|
||||
private final IndicatorsUgqCreateLoadService indicatorsUgqCreateLoadService;
|
||||
private final IndicatorsMetaverServerLoadService indicatorsMetaverServerLoadService;
|
||||
private final IndicatorsRetentionLoadService indicatorsRetentionLoadService;
|
||||
|
||||
private final IndicatorsDauService dauService;
|
||||
private final IndicatorsNruService indicatorsNruService;
|
||||
@@ -167,27 +170,25 @@ public class IndicatorsService {
|
||||
}
|
||||
// 유저 지표 Retention
|
||||
public IndicatorsResponse retentionList(Map<String, String> requestParams){
|
||||
LocalDateTime startDt = DateUtils.stringISOToLocalDateTime(requestParams.get("start_dt"));
|
||||
LocalDateTime endDt = DateUtils.stringISOToLocalDateTime(requestParams.get("end_dt"));
|
||||
List<RetentionInfo> retentionLogList = indicatorsRetentionLoadService.getIndicatorsLogData(
|
||||
startDt.toString().substring(0, 10),
|
||||
endDt.toString().substring(0, 10),
|
||||
RetentionInfo.class);
|
||||
|
||||
List<IndicatorsResponse.Retention> retentionList = new ArrayList<>();
|
||||
String startDt = CommonUtils.objectToString(requestParams.get("start_dt"));
|
||||
String endDt = CommonUtils.objectToString(requestParams.get("end_dt"));
|
||||
List<LocalDate> dateRange = CommonUtils.dateRange(startDt, endDt);
|
||||
int cnt = dateRange.size();
|
||||
for (LocalDate date : dateRange) {
|
||||
List<IndicatorsResponse.Dday> dDayList = new ArrayList<>();
|
||||
for (int i = 0; i < cnt; i++){
|
||||
IndicatorsResponse.Dday dDay = IndicatorsResponse.Dday.builder()
|
||||
.date("D+" + i)
|
||||
.dif("0%")
|
||||
.build();
|
||||
dDayList.add(dDay);
|
||||
}
|
||||
cnt -- ;
|
||||
IndicatorsResponse.Retention retention = IndicatorsResponse.Retention.builder()
|
||||
.date(date)
|
||||
.dDay(dDayList)
|
||||
for (RetentionInfo info : retentionLogList){
|
||||
IndicatorsResponse.Retention build = IndicatorsResponse.Retention.builder()
|
||||
.logDay(info.getLogDay())
|
||||
.totalCreated(info.getTotalCreate())
|
||||
.d1_rate(info.getD1_rate())
|
||||
.d7_rate(info.getD7_rate())
|
||||
.d30_rate(info.getD30_rate())
|
||||
.build();
|
||||
retentionList.add(retention);
|
||||
retentionList.add(build);
|
||||
}
|
||||
|
||||
return IndicatorsResponse.builder()
|
||||
.resultData(IndicatorsResponse.ResultData.builder()
|
||||
.retentionList(retentionList)
|
||||
@@ -202,29 +203,29 @@ public class IndicatorsService {
|
||||
String startDt = CommonUtils.objectToString(requestParams.get("start_dt"));
|
||||
String endDt = CommonUtils.objectToString(requestParams.get("end_dt"));
|
||||
List<LocalDate> dateRange = CommonUtils.dateRange(startDt, endDt);
|
||||
int cnt = dateRange.size();
|
||||
for (LocalDate date : dateRange) {
|
||||
List<IndicatorsResponse.Dday> dDayList = new ArrayList<>();
|
||||
for (int i = 0; i < cnt; i++){
|
||||
IndicatorsResponse.Dday dDay = IndicatorsResponse.Dday.builder()
|
||||
.date("D+" + i)
|
||||
.dif("0%")
|
||||
.build();
|
||||
dDayList.add(dDay);
|
||||
}
|
||||
cnt -- ;
|
||||
IndicatorsResponse.Retention retention = IndicatorsResponse.Retention.builder()
|
||||
.date(date)
|
||||
.dDay(dDayList)
|
||||
.build();
|
||||
retentionList.add(retention);
|
||||
}
|
||||
// 엑셀 파일 생성 및 다운로드
|
||||
try {
|
||||
ExcelUtils.exportToExcelByRentention(retentionList,res);
|
||||
}catch (IOException exception){
|
||||
throw new RestApiException(CommonCode.ERROR.getHttpStatus(), ErrorCode.ERROR_EXCEL_DOWN.getMessage());
|
||||
}
|
||||
// int cnt = dateRange.size();
|
||||
// for (LocalDate date : dateRange) {
|
||||
// List<IndicatorsResponse.Dday> dDayList = new ArrayList<>();
|
||||
// for (int i = 0; i < cnt; i++){
|
||||
// IndicatorsResponse.Dday dDay = IndicatorsResponse.Dday.builder()
|
||||
// .date("D+" + i)
|
||||
// .dif("0%")
|
||||
// .build();
|
||||
// dDayList.add(dDay);
|
||||
// }
|
||||
// cnt -- ;
|
||||
// IndicatorsResponse.Retention retention = IndicatorsResponse.Retention.builder()
|
||||
// .date(date)
|
||||
// .dDay(dDayList)
|
||||
// .build();
|
||||
// retentionList.add(retention);
|
||||
// }
|
||||
// // 엑셀 파일 생성 및 다운로드
|
||||
// try {
|
||||
// ExcelUtils.exportToExcelByRentention(retentionList,res);
|
||||
// }catch (IOException exception){
|
||||
// throw new RestApiException(CommonCode.ERROR.getHttpStatus(), ErrorCode.ERROR_EXCEL_DOWN.getMessage());
|
||||
// }
|
||||
|
||||
}
|
||||
// 유저 지표 Retention
|
||||
|
||||
@@ -18,6 +18,8 @@ public class AdminConstants {
|
||||
public static final String MONGO_DB_COLLECTION_HISTORY_LOG = "historyLog";
|
||||
public static final String MONGO_DB_COLLECTION_CURRENCY = "currency";
|
||||
public static final String MONGO_DB_COLLECTION_ITEM = "item";
|
||||
public static final String MONGO_DB_COLLECTION_CREATE = "userCreate";
|
||||
public static final String MONGO_DB_COLLECTION_LOGIN = "userLogin";
|
||||
|
||||
public static final String MONGO_DB_KEY_LOGTIME = "logTime";
|
||||
public static final String MONGO_DB_KEY_LOGMONTH = "logMonth";
|
||||
|
||||
@@ -541,16 +541,16 @@ public class ExcelUtils {
|
||||
|
||||
int rowIndex = 1; // 시작 행 인덱스
|
||||
// 두 번째 라인
|
||||
for (IndicatorsResponse.Retention retention : list){
|
||||
Row row = sheet.createRow(rowIndex++);
|
||||
row.createCell(0).setCellValue("ALL");
|
||||
List<IndicatorsResponse.Dday> dDay = retention.getDDay();
|
||||
row.createCell(1).setCellValue(retention.getDate().toString());
|
||||
for (int j = 0; j < dDay.size(); j++) {
|
||||
row.createCell(j+2).setCellValue(dDay.get(j).getDif());
|
||||
}
|
||||
|
||||
}
|
||||
// for (IndicatorsResponse.Retention retention : list){
|
||||
// Row row = sheet.createRow(rowIndex++);
|
||||
// row.createCell(0).setCellValue("ALL");
|
||||
// List<IndicatorsResponse.Dday> dDay = retention.getDDay();
|
||||
// row.createCell(1).setCellValue(retention.getDate().toString());
|
||||
// for (int j = 0; j < dDay.size(); j++) {
|
||||
// row.createCell(j+2).setCellValue(dDay.get(j).getDif());
|
||||
// }
|
||||
//
|
||||
// }
|
||||
|
||||
//cell 머지
|
||||
sheet.addMergedRegion(CellRangeAddress.valueOf("A2:A"+(list.size()+1)));
|
||||
|
||||
Reference in New Issue
Block a user