Browse Source

SONAR-20156 New field previousUncompliantValue in api/new_code_periods/show

tags/10.2.0.77647
Zipeng WU 9 months ago
parent
commit
30027b2d9a

+ 23
- 19
server/sonar-db-dao/src/it/java/org/sonar/db/newcodeperiod/NewCodePeriodDaoIT.java View File



@Test @Test
public void insert_new_code_period() { public void insert_new_code_period() {
insert("1", "proj-uuid", "branch-uuid", NUMBER_OF_DAYS, "5");
insert("1", "proj-uuid", "branch-uuid", NUMBER_OF_DAYS, "5", null);


Optional<NewCodePeriodDto> resultOpt = underTest.selectByUuid(dbSession, "1"); Optional<NewCodePeriodDto> resultOpt = underTest.selectByUuid(dbSession, "1");


"defghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijab" + "defghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijabcdefghijab" +
"cdefghijabcdefghijabcdefghijabcdefghijxxxxx"; "cdefghijabcdefghijabcdefghijabcdefghijxxxxx";


insert("1", "proj-uuid", "branch-uuid", REFERENCE_BRANCH, branchWithLongName);
insert("1", "proj-uuid", "branch-uuid", REFERENCE_BRANCH, branchWithLongName, null);


assertThat(db.select("select uuid as \"UUID\", value as \"VALUE\" from new_code_periods")) assertThat(db.select("select uuid as \"UUID\", value as \"VALUE\" from new_code_periods"))
.extracting(r -> r.get("UUID"), r -> r.get("VALUE")) .extracting(r -> r.get("UUID"), r -> r.get("VALUE"))


@Test @Test
public void update_new_code_period() { public void update_new_code_period() {
insert("1", "proj-uuid", "branch-uuid", NUMBER_OF_DAYS, "5");
insert("1", "proj-uuid", "branch-uuid", NUMBER_OF_DAYS, "5", null);


underTest.update(dbSession, new NewCodePeriodDto() underTest.update(dbSession, new NewCodePeriodDto()
.setUuid("1") .setUuid("1")


@Test @Test
public void insert_with_upsert() { public void insert_with_upsert() {
insert("1", "proj-uuid", "branch-uuid", NUMBER_OF_DAYS, "5");
insert("1", "proj-uuid", "branch-uuid", NUMBER_OF_DAYS, "5", null);


Optional<NewCodePeriodDto> resultOpt = underTest.selectByUuid(dbSession, "1"); Optional<NewCodePeriodDto> resultOpt = underTest.selectByUuid(dbSession, "1");




@Test @Test
public void update_with_upsert() { public void update_with_upsert() {
insert("1", "proj-uuid", "branch-uuid", NUMBER_OF_DAYS, "5");
insert("1", "proj-uuid", "branch-uuid", NUMBER_OF_DAYS, "5", null);


underTest.upsert(dbSession, new NewCodePeriodDto() underTest.upsert(dbSession, new NewCodePeriodDto()
.setUuid("1") .setUuid("1")


@Test @Test
public void select_by_project_and_branch_uuids() { public void select_by_project_and_branch_uuids() {
insert("1", "proj-uuid", "branch-uuid", NUMBER_OF_DAYS, "5");
insert("1", "proj-uuid", "branch-uuid", NUMBER_OF_DAYS, "5", null);


Optional<NewCodePeriodDto> resultOpt = underTest.selectByBranch(dbSession, "proj-uuid", "branch-uuid"); Optional<NewCodePeriodDto> resultOpt = underTest.selectByBranch(dbSession, "proj-uuid", "branch-uuid");
assertThat(resultOpt) assertThat(resultOpt)
BranchDto branch2 = db.components().insertProjectBranch(project); BranchDto branch2 = db.components().insertProjectBranch(project);
BranchDto branch3 = db.components().insertProjectBranch(project); BranchDto branch3 = db.components().insertProjectBranch(project);


insert("1", project.getUuid(), null, REFERENCE_BRANCH, mainBranch.getKey());
insert("2", project.getUuid(), branch1.getUuid(), REFERENCE_BRANCH, mainBranch.getKey());
insert("3", project.getUuid(), branch2.getUuid(), NUMBER_OF_DAYS, "5");
insert("4", project.getUuid(), project.getUuid(), PREVIOUS_VERSION, null);
insert("1", project.getUuid(), null, REFERENCE_BRANCH, mainBranch.getKey(), null);
insert("2", project.getUuid(), branch1.getUuid(), REFERENCE_BRANCH, mainBranch.getKey(), null);
insert("3", project.getUuid(), branch2.getUuid(), NUMBER_OF_DAYS, "5", null);
insert("4", project.getUuid(), project.getUuid(), PREVIOUS_VERSION, null, null);
db.commit(); db.commit();
assertThat(underTest.selectBranchesReferencing(dbSession, project.getUuid(), mainBranch.getKey())).containsOnly(branch1.getUuid(), branch3.getUuid()); assertThat(underTest.selectBranchesReferencing(dbSession, project.getUuid(), mainBranch.getKey())).containsOnly(branch1.getUuid(), branch3.getUuid());
} }


@Test @Test
public void select_by_project_uuid() { public void select_by_project_uuid() {
insert("1", "proj-uuid", null, NUMBER_OF_DAYS, "5");
insert("1", "proj-uuid", null, NUMBER_OF_DAYS, "90", "130");


Optional<NewCodePeriodDto> resultOpt = underTest.selectByProject(dbSession, "proj-uuid"); Optional<NewCodePeriodDto> resultOpt = underTest.selectByProject(dbSession, "proj-uuid");
assertThat(resultOpt) assertThat(resultOpt)
assertThat(result.getProjectUuid()).isEqualTo("proj-uuid"); assertThat(result.getProjectUuid()).isEqualTo("proj-uuid");
assertThat(result.getBranchUuid()).isNull(); assertThat(result.getBranchUuid()).isNull();
assertThat(result.getType()).isEqualTo(NUMBER_OF_DAYS); assertThat(result.getType()).isEqualTo(NUMBER_OF_DAYS);
assertThat(result.getValue()).isEqualTo("5");
assertThat(result.getValue()).isEqualTo("90");
assertThat(result.getPreviousNonCompliantValue()).isEqualTo("130");
assertThat(result.getCreatedAt()).isNotZero(); assertThat(result.getCreatedAt()).isNotZero();
assertThat(result.getUpdatedAt()).isNotZero(); assertThat(result.getUpdatedAt()).isNotZero();
} }


@Test @Test
public void select_global() { public void select_global() {
insert("1", null, null, NUMBER_OF_DAYS, "30");
insert("1", null, null, NUMBER_OF_DAYS, "30", null);


Optional<NewCodePeriodDto> newCodePeriodDto = underTest.selectGlobal(dbSession); Optional<NewCodePeriodDto> newCodePeriodDto = underTest.selectGlobal(dbSession);
assertThat(newCodePeriodDto).isNotEmpty(); assertThat(newCodePeriodDto).isNotEmpty();
assertThat(result.getBranchUuid()).isNull(); assertThat(result.getBranchUuid()).isNull();
assertThat(result.getType()).isEqualTo(NUMBER_OF_DAYS); assertThat(result.getType()).isEqualTo(NUMBER_OF_DAYS);
assertThat(result.getValue()).isEqualTo("30"); assertThat(result.getValue()).isEqualTo("30");
assertThat(result.getPreviousNonCompliantValue()).isNull();
assertThat(result.getCreatedAt()).isNotZero(); assertThat(result.getCreatedAt()).isNotZero();
assertThat(result.getUpdatedAt()).isNotZero(); assertThat(result.getUpdatedAt()).isNotZero();
} }


@Test @Test
public void exists_by_project_analysis_is_true() { public void exists_by_project_analysis_is_true() {
insert("1", "proj-uuid", "branch-uuid", SPECIFIC_ANALYSIS, "analysis-uuid");
insert("1", "proj-uuid", "branch-uuid", SPECIFIC_ANALYSIS, "analysis-uuid", null);


boolean exists = underTest.existsByProjectAnalysisUuid(dbSession, "analysis-uuid"); boolean exists = underTest.existsByProjectAnalysisUuid(dbSession, "analysis-uuid");
assertThat(exists).isTrue(); assertThat(exists).isTrue();


@Test @Test
public void delete_by_project_uuid_and_branch_uuid() { public void delete_by_project_uuid_and_branch_uuid() {
insert("1", "proj-uuid", "branch-uuid", SPECIFIC_ANALYSIS, "analysis-uuid");
insert("1", "proj-uuid", "branch-uuid", SPECIFIC_ANALYSIS, "analysis-uuid", null);


underTest.delete(dbSession, "proj-uuid", "branch-uuid"); underTest.delete(dbSession, "proj-uuid", "branch-uuid");
db.commit(); db.commit();


@Test @Test
public void delete_by_project_uuid() { public void delete_by_project_uuid() {
insert("1", "proj-uuid", null, SPECIFIC_ANALYSIS, "analysis-uuid");
insert("1", "proj-uuid", null, SPECIFIC_ANALYSIS, "analysis-uuid", null);


underTest.delete(dbSession, "proj-uuid", null); underTest.delete(dbSession, "proj-uuid", null);
db.commit(); db.commit();


@Test @Test
public void delete_global() { public void delete_global() {
insert("1", null, null, SPECIFIC_ANALYSIS, "analysis-uuid");
insert("1", null, null, SPECIFIC_ANALYSIS, "analysis-uuid", null);


underTest.delete(dbSession, null, null); underTest.delete(dbSession, null, null);
db.commit(); db.commit();
.isEqualTo(expected); .isEqualTo(expected);
} }


private void insert(String uuid, @Nullable String projectUuid, @Nullable String branchUuid, NewCodePeriodType type, @Nullable String value) {
private void insert(String uuid, @Nullable String projectUuid, @Nullable String branchUuid, NewCodePeriodType type,
@Nullable String value, @Nullable String previousNonCompliantValue) {
underTest.insert(dbSession, new NewCodePeriodDto() underTest.insert(dbSession, new NewCodePeriodDto()
.setUuid(uuid) .setUuid(uuid)
.setProjectUuid(projectUuid) .setProjectUuid(projectUuid)
.setBranchUuid(branchUuid) .setBranchUuid(branchUuid)
.setType(type) .setType(type)
.setValue(value));
.setValue(value)
.setPreviousNonCompliantValue(previousNonCompliantValue));
db.commit(); db.commit();
} }
} }

+ 10
- 0
server/sonar-db-dao/src/main/java/org/sonar/db/newcodeperiod/NewCodePeriodDto.java View File

private String projectUuid = null; private String projectUuid = null;
private String branchUuid = null; private String branchUuid = null;
private NewCodePeriodType type = null; private NewCodePeriodType type = null;
private String previousNonCompliantValue = null;
private String value = null; private String value = null;
private long updatedAt = 0L; private long updatedAt = 0L;
private long createdAt = 0L; private long createdAt = 0L;
this.value = value; this.value = value;
return this; return this;
} }

public String getPreviousNonCompliantValue() {
return previousNonCompliantValue;
}

public NewCodePeriodDto setPreviousNonCompliantValue(String previousNonCompliantValue) {
this.previousNonCompliantValue = previousNonCompliantValue;
return this;
}
} }

+ 4
- 1
server/sonar-db-dao/src/main/resources/org/sonar/db/newcodeperiod/NewCodePeriodMapper.xml View File

ncp.branch_uuid as branchUuid, ncp.branch_uuid as branchUuid,
ncp.type, ncp.type,
ncp.value, ncp.value,
ncp.previous_non_compliant_value as previousNonCompliantValue,
ncp.updated_at as updatedAt, ncp.updated_at as updatedAt,
ncp.created_at as createdAt ncp.created_at as createdAt
</sql> </sql>


<insert id="insert" parameterType="org.sonar.db.newcodeperiod.NewCodePeriodDto"> <insert id="insert" parameterType="org.sonar.db.newcodeperiod.NewCodePeriodDto">
INSERT INTO new_code_periods ( INSERT INTO new_code_periods (
uuid, project_uuid, branch_uuid, type, value, updated_at, created_at)
uuid, project_uuid, branch_uuid, type, value, previous_non_compliant_value, updated_at, created_at)
VALUES ( VALUES (
#{uuid, jdbcType=VARCHAR}, #{uuid, jdbcType=VARCHAR},
#{projectUuid, jdbcType=VARCHAR}, #{projectUuid, jdbcType=VARCHAR},
#{branchUuid, jdbcType=VARCHAR}, #{branchUuid, jdbcType=VARCHAR},
#{type, jdbcType=VARCHAR}, #{type, jdbcType=VARCHAR},
#{value, jdbcType=VARCHAR}, #{value, jdbcType=VARCHAR},
#{previousNonCompliantValue, jdbcType=VARCHAR},
#{updatedAt, jdbcType=TIMESTAMP}, #{updatedAt, jdbcType=TIMESTAMP},
#{createdAt, jdbcType=TIMESTAMP} #{createdAt, jdbcType=TIMESTAMP}
) )
SET SET
type=#{type, jdbcType=VARCHAR}, type=#{type, jdbcType=VARCHAR},
value=#{value, jdbcType=VARCHAR}, value=#{value, jdbcType=VARCHAR},
previous_non_compliant_value=#{previousNonCompliantValue, jdbcType=VARCHAR},
updated_at=#{updatedAt, jdbcType=TIMESTAMP} updated_at=#{updatedAt, jdbcType=TIMESTAMP}
WHERE WHERE
<choose> <choose>

+ 28
- 0
server/sonar-webserver-webapi/src/it/java/org/sonar/server/newcodeperiod/ws/SetActionIT.java View File

import org.sonar.db.component.ProjectData; import org.sonar.db.component.ProjectData;
import org.sonar.db.component.SnapshotDto; import org.sonar.db.component.SnapshotDto;
import org.sonar.db.newcodeperiod.NewCodePeriodDao; import org.sonar.db.newcodeperiod.NewCodePeriodDao;
import org.sonar.db.newcodeperiod.NewCodePeriodDbTester;
import org.sonar.db.newcodeperiod.NewCodePeriodDto;
import org.sonar.db.newcodeperiod.NewCodePeriodType; import org.sonar.db.newcodeperiod.NewCodePeriodType;
import org.sonar.db.project.ProjectDto; import org.sonar.db.project.ProjectDto;
import org.sonar.server.component.ComponentFinder; import org.sonar.server.component.ComponentFinder;
assertTableContainsOnly(project.getUuid(), null, NewCodePeriodType.NUMBER_OF_DAYS, "5"); assertTableContainsOnly(project.getUuid(), null, NewCodePeriodType.NUMBER_OF_DAYS, "5");
} }


@Test
public void update_project_new_code_period() {
ProjectDto project = db.components().insertPublicProject().getProjectDto();
logInAsProjectAdministrator(project);
var currentTime = System.currentTimeMillis();

db.newCodePeriods().insert(new NewCodePeriodDto()
.setProjectUuid(project.getUuid())
.setType(NewCodePeriodType.NUMBER_OF_DAYS)
.setPreviousNonCompliantValue("100")
.setUpdatedAt(currentTime)
.setValue("90"));

ws.newRequest()
.setParam("project", project.getKey())
.setParam("type", "number_of_days")
.setParam("value", "30")
.execute();

var ncd = db.getDbClient().newCodePeriodDao().selectByProject(dbSession, project.getUuid());
assertThat(ncd).isPresent();
assertThat(ncd.get()).extracting(NewCodePeriodDto::getType, NewCodePeriodDto::getValue, NewCodePeriodDto::getPreviousNonCompliantValue)
.containsExactly(NewCodePeriodType.NUMBER_OF_DAYS, "30", null);

}

@Test @Test
@UseDataProvider("provideNewCodePeriodTypeAndValue") @UseDataProvider("provideNewCodePeriodTypeAndValue")
public void never_set_project_value_in_community_edition(NewCodePeriodType type, @Nullable String value) { public void never_set_project_value_in_community_edition(NewCodePeriodType type, @Nullable String value) {

+ 23
- 11
server/sonar-webserver-webapi/src/it/java/org/sonar/server/newcodeperiod/ws/ShowActionIT.java View File

import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import static org.sonarqube.ws.NewCodePeriods.NewCodePeriodType.NUMBER_OF_DAYS;
import static org.sonarqube.ws.NewCodePeriods.NewCodePeriodType.PREVIOUS_VERSION;


public class ShowActionIT { public class ShowActionIT {
@Rule @Rule
private WsActionTester ws; private WsActionTester ws;


@Before @Before
public void setup(){
public void setup() {
when(documentationLinkGenerator.getDocumentationLink(any())).thenReturn("https://docs.sonarqube.org/latest/project-administration/defining-new-code/"); when(documentationLinkGenerator.getDocumentationLink(any())).thenReturn("https://docs.sonarqube.org/latest/project-administration/defining-new-code/");
ws = new WsActionTester(new ShowAction(dbClient, userSession, componentFinder, dao, documentationLinkGenerator)); ws = new WsActionTester(new ShowAction(dbClient, userSession, componentFinder, dao, documentationLinkGenerator));
} }
ShowWSResponse response = ws.newRequest() ShowWSResponse response = ws.newRequest()
.executeProtobuf(ShowWSResponse.class); .executeProtobuf(ShowWSResponse.class);


assertResponse(response, "", "", NewCodePeriods.NewCodePeriodType.PREVIOUS_VERSION, "", false);
assertResponse(response, "", "", PREVIOUS_VERSION, "", false, null);
} }


@Test @Test
public void show_project_setting() { public void show_project_setting() {
ProjectDto project = db.components().insertPublicProject().getProjectDto(); ProjectDto project = db.components().insertPublicProject().getProjectDto();
logInAsProjectAdministrator(project); logInAsProjectAdministrator(project);
var currentTime = System.currentTimeMillis();


tester.insert(new NewCodePeriodDto() tester.insert(new NewCodePeriodDto()
.setProjectUuid(project.getUuid()) .setProjectUuid(project.getUuid())
.setType(NewCodePeriodType.NUMBER_OF_DAYS) .setType(NewCodePeriodType.NUMBER_OF_DAYS)
.setValue("4"));
.setPreviousNonCompliantValue("100")
.setUpdatedAt(currentTime)
.setValue("90"));


ShowWSResponse response = ws.newRequest() ShowWSResponse response = ws.newRequest()
.setParam("project", project.getKey()) .setParam("project", project.getKey())
.executeProtobuf(ShowWSResponse.class); .executeProtobuf(ShowWSResponse.class);


assertResponse(response, project.getKey(), "", NewCodePeriods.NewCodePeriodType.NUMBER_OF_DAYS, "4", false);
assertResponse(response, project.getKey(), "", NUMBER_OF_DAYS, "90", false, "100");
assertUpdatedAt(response, currentTime);
} }


@Test @Test
.setParam("branch", "branch") .setParam("branch", "branch")
.executeProtobuf(ShowWSResponse.class); .executeProtobuf(ShowWSResponse.class);


assertResponse(response, project.getKey(), "branch", NewCodePeriods.NewCodePeriodType.NUMBER_OF_DAYS, "1", false);
assertResponse(response, project.getKey(), "branch", NUMBER_OF_DAYS, "1", false, null);
} }


@Test @Test
.setParam("project", project.getKey()) .setParam("project", project.getKey())
.executeProtobuf(ShowWSResponse.class); .executeProtobuf(ShowWSResponse.class);


assertResponse(response, project.getKey(), "", NewCodePeriods.NewCodePeriodType.PREVIOUS_VERSION, "", true);
assertResponse(response, project.getKey(), "", PREVIOUS_VERSION, "", true, null);
} }


@Test @Test
.setParam("branch", "branch") .setParam("branch", "branch")
.executeProtobuf(ShowWSResponse.class); .executeProtobuf(ShowWSResponse.class);


assertResponse(response, project.getKey(), "branch", NewCodePeriods.NewCodePeriodType.NUMBER_OF_DAYS, "1", true);
assertResponse(response, project.getKey(), "branch", NUMBER_OF_DAYS, "1", true, null);
} }


@Test @Test
.setParam("branch", "branch") .setParam("branch", "branch")
.executeProtobuf(ShowWSResponse.class); .executeProtobuf(ShowWSResponse.class);


assertResponse(response, project.getKey(), "branch", NewCodePeriods.NewCodePeriodType.NUMBER_OF_DAYS, "3", true);
assertResponse(response, project.getKey(), "branch", NUMBER_OF_DAYS, "3", true, null);
} }


@Test @Test
.setParam("project", "unknown") .setParam("project", "unknown")
.executeProtobuf(ShowWSResponse.class); .executeProtobuf(ShowWSResponse.class);


assertResponse(response, "", "", NewCodePeriods.NewCodePeriodType.NUMBER_OF_DAYS, "3", true);
assertResponse(response, "", "", NUMBER_OF_DAYS, "3", true, null);
} }


@Test @Test
.setParam("branch", "unknown") .setParam("branch", "unknown")
.executeProtobuf(ShowWSResponse.class); .executeProtobuf(ShowWSResponse.class);


assertResponse(response, project.getKey(), "", NewCodePeriods.NewCodePeriodType.NUMBER_OF_DAYS, "3", true);
assertResponse(response, project.getKey(), "", NUMBER_OF_DAYS, "3", true, null);
} }


private void assertResponse(ShowWSResponse response, String projectKey, String branchKey, NewCodePeriods.NewCodePeriodType type, String value, boolean inherited) {
private void assertResponse(ShowWSResponse response, String projectKey, String branchKey, NewCodePeriods.NewCodePeriodType type, String value, boolean inherited,
String previousNonCompliantValue) {
assertThat(response.getBranchKey()).isEqualTo(branchKey); assertThat(response.getBranchKey()).isEqualTo(branchKey);
assertThat(response.getProjectKey()).isEqualTo(projectKey); assertThat(response.getProjectKey()).isEqualTo(projectKey);
assertThat(response.getInherited()).isEqualTo(inherited); assertThat(response.getInherited()).isEqualTo(inherited);
assertThat(response.getValue()).isEqualTo(value); assertThat(response.getValue()).isEqualTo(value);
assertThat(response.getType()).isEqualTo(type); assertThat(response.getType()).isEqualTo(type);
assertThat(response.getPreviousNonCompliantValue()).isEqualTo(previousNonCompliantValue == null ? "" : previousNonCompliantValue);
}

private static void assertUpdatedAt(ShowWSResponse response, long currentTime) {
assertThat(response.getUpdatedAt()).isEqualTo(currentTime);
} }


private void logInAsProjectAdministrator(ProjectDto project) { private void logInAsProjectAdministrator(ProjectDto project) {

+ 6
- 2
server/sonar-webserver-webapi/src/main/java/org/sonar/server/newcodeperiod/ws/ShowAction.java View File



private void checkPermission(ProjectDto project) { private void checkPermission(ProjectDto project) {
if (userSession.hasEntityPermission(UserRole.SCAN, project) || if (userSession.hasEntityPermission(UserRole.SCAN, project) ||
userSession.hasEntityPermission(UserRole.ADMIN, project) ||
userSession.hasPermission(SCAN)) {
userSession.hasEntityPermission(UserRole.ADMIN, project) ||
userSession.hasPermission(SCAN)) {
return; return;
} }
throw insufficientPrivilegesException(); throw insufficientPrivilegesException();
private static ShowWSResponse.Builder build(NewCodePeriodDto dto, boolean inherited) { private static ShowWSResponse.Builder build(NewCodePeriodDto dto, boolean inherited) {
ShowWSResponse.Builder builder = ShowWSResponse.newBuilder() ShowWSResponse.Builder builder = ShowWSResponse.newBuilder()
.setType(convertType(dto.getType())) .setType(convertType(dto.getType()))
.setUpdatedAt(dto.getUpdatedAt())
.setInherited(inherited); .setInherited(inherited);


if (dto.getValue() != null) { if (dto.getValue() != null) {
builder.setValue(dto.getValue()); builder.setValue(dto.getValue());
} }
if (dto.getPreviousNonCompliantValue() != null) {
builder.setPreviousNonCompliantValue(dto.getPreviousNonCompliantValue());
}
return builder; return builder;
} }



+ 2
- 0
sonar-ws/src/main/protobuf/ws-newcodeperiods.proto View File

string value = 4; string value = 4;
bool inherited = 5; bool inherited = 5;
string effectiveValue = 6; string effectiveValue = 6;
string previousNonCompliantValue = 7;
int64 updatedAt = 8;
} }


// WS api/new_code_periods/list // WS api/new_code_periods/list

Loading…
Cancel
Save