Browse Source
# Conflicts: # dolphinscheduler-service/src/main/resources/logback-zookeeper.xmlpull/3/MERGE
dailidong
4 years ago
127 changed files with 2713 additions and 271 deletions
@ -0,0 +1,81 @@
|
||||
/* |
||||
* Licensed to the Apache Software Foundation (ASF) under one or more |
||||
* contributor license agreements. See the NOTICE file distributed with |
||||
* this work for additional information regarding copyright ownership. |
||||
* The ASF licenses this file to You under the Apache License, Version 2.0 |
||||
* (the "License"); you may not use this file except in compliance with |
||||
* the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package org.apache.dolphinscheduler.api.controller; |
||||
|
||||
import org.apache.dolphinscheduler.api.service.WorkFlowLineageService; |
||||
import org.apache.dolphinscheduler.api.utils.Result; |
||||
import org.apache.dolphinscheduler.common.utils.ParameterUtils; |
||||
import io.swagger.annotations.ApiParam; |
||||
import org.apache.dolphinscheduler.dao.entity.WorkFlowLineage; |
||||
import org.slf4j.Logger; |
||||
import org.slf4j.LoggerFactory; |
||||
import org.springframework.beans.factory.annotation.Autowired; |
||||
import org.springframework.http.HttpStatus; |
||||
import org.springframework.web.bind.annotation.*; |
||||
import springfox.documentation.annotations.ApiIgnore; |
||||
|
||||
import java.util.HashSet; |
||||
import java.util.List; |
||||
import java.util.Map; |
||||
import java.util.Set; |
||||
|
||||
import static org.apache.dolphinscheduler.api.enums.Status.QUERY_WORKFLOW_LINEAGE_ERROR; |
||||
|
||||
@RestController |
||||
@RequestMapping("lineages/{projectId}") |
||||
public class WorkFlowLineageController extends BaseController { |
||||
private static final Logger logger = LoggerFactory.getLogger(WorkFlowLineageController.class); |
||||
|
||||
@Autowired |
||||
private WorkFlowLineageService workFlowLineageService; |
||||
|
||||
@GetMapping(value="/list-name") |
||||
@ResponseStatus(HttpStatus.OK) |
||||
public Result<List<WorkFlowLineage>> queryWorkFlowLineageByName(@ApiIgnore @RequestParam(value = "searchVal", required = false) String searchVal, @ApiParam(name = "projectId", value = "PROJECT_ID", required = true) @PathVariable int projectId) { |
||||
try { |
||||
searchVal = ParameterUtils.handleEscapes(searchVal); |
||||
Map<String, Object> result = workFlowLineageService.queryWorkFlowLineageByName(searchVal,projectId); |
||||
return returnDataList(result); |
||||
} catch (Exception e){ |
||||
logger.error(QUERY_WORKFLOW_LINEAGE_ERROR.getMsg(),e); |
||||
return error(QUERY_WORKFLOW_LINEAGE_ERROR.getCode(), QUERY_WORKFLOW_LINEAGE_ERROR.getMsg()); |
||||
} |
||||
} |
||||
|
||||
@GetMapping(value="/list-ids") |
||||
@ResponseStatus(HttpStatus.OK) |
||||
public Result<Map<String, Object>> queryWorkFlowLineageByIds(@ApiIgnore @RequestParam(value = "ids", required = false) String ids,@ApiParam(name = "projectId", value = "PROJECT_ID", required = true) @PathVariable int projectId) { |
||||
|
||||
try { |
||||
ids = ParameterUtils.handleEscapes(ids); |
||||
Set<Integer> idsSet = new HashSet<>(); |
||||
if(ids != null) { |
||||
String[] idsStr = ids.split(","); |
||||
for (String id : idsStr) |
||||
{ |
||||
idsSet.add(Integer.parseInt(id)); |
||||
} |
||||
} |
||||
|
||||
Map<String, Object> result = workFlowLineageService.queryWorkFlowLineageByIds(idsSet, projectId); |
||||
return returnDataList(result); |
||||
} catch (Exception e){ |
||||
logger.error(QUERY_WORKFLOW_LINEAGE_ERROR.getMsg(),e); |
||||
return error(QUERY_WORKFLOW_LINEAGE_ERROR.getCode(), QUERY_WORKFLOW_LINEAGE_ERROR.getMsg()); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,97 @@
|
||||
/* |
||||
* Licensed to the Apache Software Foundation (ASF) under one or more |
||||
* contributor license agreements. See the NOTICE file distributed with |
||||
* this work for additional information regarding copyright ownership. |
||||
* The ASF licenses this file to You under the Apache License, Version 2.0 |
||||
* (the "License"); you may not use this file except in compliance with |
||||
* the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package org.apache.dolphinscheduler.api.service; |
||||
|
||||
import org.apache.dolphinscheduler.api.enums.Status; |
||||
import org.apache.dolphinscheduler.common.Constants; |
||||
import org.apache.dolphinscheduler.dao.mapper.WorkFlowLineageMapper; |
||||
import org.apache.dolphinscheduler.dao.entity.WorkFlowLineage; |
||||
import org.apache.dolphinscheduler.dao.entity.WorkFlowRelation; |
||||
import org.springframework.beans.factory.annotation.Autowired; |
||||
import org.springframework.stereotype.Service; |
||||
|
||||
import java.util.*; |
||||
|
||||
@Service |
||||
public class WorkFlowLineageService extends BaseService { |
||||
|
||||
@Autowired |
||||
private WorkFlowLineageMapper workFlowLineageMapper; |
||||
|
||||
public Map<String, Object> queryWorkFlowLineageByName(String workFlowName, int projectId) { |
||||
Map<String, Object> result = new HashMap<>(5); |
||||
List<WorkFlowLineage> workFlowLineageList = workFlowLineageMapper.queryByName(workFlowName, projectId); |
||||
result.put(Constants.DATA_LIST, workFlowLineageList); |
||||
putMsg(result, Status.SUCCESS); |
||||
return result; |
||||
} |
||||
|
||||
private List<WorkFlowRelation> getWorkFlowRelationRecursion(Set<Integer> ids, List<WorkFlowRelation> workFlowRelations,Set<Integer> sourceIds) { |
||||
for(int id : ids) { |
||||
sourceIds.addAll(ids); |
||||
List<WorkFlowRelation> workFlowRelationsTmp = workFlowLineageMapper.querySourceTarget(id); |
||||
if(workFlowRelationsTmp != null && !workFlowRelationsTmp.isEmpty()) { |
||||
Set<Integer> idsTmp = new HashSet<>(); |
||||
for(WorkFlowRelation workFlowRelation:workFlowRelationsTmp) { |
||||
if(!sourceIds.contains(workFlowRelation.getTargetWorkFlowId())){ |
||||
idsTmp.add(workFlowRelation.getTargetWorkFlowId()); |
||||
} |
||||
} |
||||
workFlowRelations.addAll(workFlowRelationsTmp); |
||||
getWorkFlowRelationRecursion(idsTmp, workFlowRelations,sourceIds); |
||||
} |
||||
} |
||||
return workFlowRelations; |
||||
} |
||||
|
||||
public Map<String, Object> queryWorkFlowLineageByIds(Set<Integer> ids,int projectId) { |
||||
Map<String, Object> result = new HashMap<>(5); |
||||
List<WorkFlowLineage> workFlowLineageList = workFlowLineageMapper.queryByIds(ids, projectId); |
||||
Map<String, Object> workFlowLists = new HashMap<>(5); |
||||
Set<Integer> idsV = new HashSet<>(); |
||||
if(ids == null || ids.isEmpty()){ |
||||
for(WorkFlowLineage workFlowLineage:workFlowLineageList) { |
||||
idsV.add(workFlowLineage.getWorkFlowId()); |
||||
} |
||||
} else { |
||||
idsV = ids; |
||||
} |
||||
List<WorkFlowRelation> workFlowRelations = new ArrayList<>(); |
||||
Set<Integer> sourceIds = new HashSet<>(); |
||||
getWorkFlowRelationRecursion(idsV, workFlowRelations, sourceIds); |
||||
|
||||
Set<Integer> idSet = new HashSet<>(); |
||||
//If the incoming parameter is not empty, you need to add downstream workflow detail attributes
|
||||
if(ids != null && !ids.isEmpty()) { |
||||
for(WorkFlowRelation workFlowRelation : workFlowRelations) { |
||||
idSet.add(workFlowRelation.getTargetWorkFlowId()); |
||||
} |
||||
for(int id : ids){ |
||||
idSet.remove(id); |
||||
} |
||||
if(!idSet.isEmpty()) { |
||||
workFlowLineageList.addAll(workFlowLineageMapper.queryByIds(idSet, projectId)); |
||||
} |
||||
} |
||||
|
||||
workFlowLists.put("workFlowList",workFlowLineageList); |
||||
workFlowLists.put("workFlowRelationList",workFlowRelations); |
||||
result.put(Constants.DATA_LIST, workFlowLists); |
||||
putMsg(result, Status.SUCCESS); |
||||
return result; |
||||
} |
||||
} |
@ -0,0 +1,69 @@
|
||||
/* |
||||
* Licensed to the Apache Software Foundation (ASF) under one or more |
||||
* contributor license agreements. See the NOTICE file distributed with |
||||
* this work for additional information regarding copyright ownership. |
||||
* The ASF licenses this file to You under the Apache License, Version 2.0 |
||||
* (the "License"); you may not use this file except in compliance with |
||||
* the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package org.apache.dolphinscheduler.api.controller; |
||||
|
||||
import org.apache.dolphinscheduler.api.enums.Status; |
||||
import org.apache.dolphinscheduler.api.utils.Result; |
||||
import org.apache.dolphinscheduler.common.utils.JSONUtils; |
||||
import org.junit.Assert; |
||||
import org.junit.Test; |
||||
import org.slf4j.Logger; |
||||
import org.slf4j.LoggerFactory; |
||||
import org.springframework.http.MediaType; |
||||
import org.springframework.test.web.servlet.MvcResult; |
||||
import org.springframework.util.LinkedMultiValueMap; |
||||
import org.springframework.util.MultiValueMap; |
||||
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; |
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; |
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; |
||||
|
||||
public class WorkFlowLineageControllerTest extends AbstractControllerTest { |
||||
private static Logger logger = LoggerFactory.getLogger(WorkFlowLineageControllerTest.class); |
||||
|
||||
@Test |
||||
public void testQueryWorkFlowLineageByName() throws Exception { |
||||
MultiValueMap<String, String> paramsMap = new LinkedMultiValueMap<>(); |
||||
paramsMap.add("searchVal","test"); |
||||
MvcResult mvcResult = mockMvc.perform(get("/lineages/1/list-name") |
||||
.header("sessionId", sessionId) |
||||
.params(paramsMap)) |
||||
.andExpect(status().isOk()) |
||||
.andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8)) |
||||
.andReturn(); |
||||
Result result = JSONUtils.parseObject(mvcResult.getResponse().getContentAsString(), Result.class); |
||||
Assert.assertEquals(Status.SUCCESS.getCode(),result.getCode().intValue()); |
||||
logger.info(mvcResult.getResponse().getContentAsString()); |
||||
|
||||
} |
||||
|
||||
@Test |
||||
public void testQueryWorkFlowLineageByIds() throws Exception { |
||||
MultiValueMap<String, String> paramsMap = new LinkedMultiValueMap<>(); |
||||
paramsMap.add("ids","1"); |
||||
MvcResult mvcResult = mockMvc.perform(get("/lineages/1/list-ids") |
||||
.header("sessionId", sessionId) |
||||
.params(paramsMap)) |
||||
.andExpect(status().isOk()) |
||||
.andExpect(content().contentType(MediaType.APPLICATION_JSON_UTF8)) |
||||
.andReturn(); |
||||
Result result = JSONUtils.parseObject(mvcResult.getResponse().getContentAsString(), Result.class); |
||||
Assert.assertEquals(Status.SUCCESS.getCode(),result.getCode().intValue()); |
||||
logger.info(mvcResult.getResponse().getContentAsString()); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,88 @@
|
||||
/* |
||||
* Licensed to the Apache Software Foundation (ASF) under one or more |
||||
* contributor license agreements. See the NOTICE file distributed with |
||||
* this work for additional information regarding copyright ownership. |
||||
* The ASF licenses this file to You under the Apache License, Version 2.0 |
||||
* (the "License"); you may not use this file except in compliance with |
||||
* the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package org.apache.dolphinscheduler.api.service; |
||||
|
||||
import org.apache.dolphinscheduler.common.Constants; |
||||
import org.apache.dolphinscheduler.common.utils.EncryptionUtils; |
||||
import org.apache.dolphinscheduler.dao.entity.WorkFlowLineage; |
||||
import org.apache.dolphinscheduler.dao.entity.WorkFlowRelation; |
||||
import org.apache.dolphinscheduler.dao.mapper.WorkFlowLineageMapper; |
||||
import org.junit.Assert; |
||||
import org.junit.Test; |
||||
import org.junit.runner.RunWith; |
||||
import org.mockito.InjectMocks; |
||||
import org.mockito.Mock; |
||||
import org.mockito.junit.MockitoJUnitRunner; |
||||
|
||||
import java.util.*; |
||||
|
||||
import static org.mockito.Mockito.when; |
||||
|
||||
@RunWith(MockitoJUnitRunner.class) |
||||
public class WorkFlowLineageServiceTest { |
||||
|
||||
@InjectMocks |
||||
private WorkFlowLineageService workFlowLineageService; |
||||
|
||||
@Mock |
||||
private WorkFlowLineageMapper workFlowLineageMapper; |
||||
|
||||
@Test |
||||
public void testQueryWorkFlowLineageByName() { |
||||
String searchVal = "test"; |
||||
when(workFlowLineageMapper.queryByName(searchVal, 1)).thenReturn(getWorkFlowLineages()); |
||||
Map<String, Object> result = workFlowLineageService.queryWorkFlowLineageByName(searchVal,1); |
||||
List<WorkFlowLineage> workFlowLineageList = (List<WorkFlowLineage>)result.get(Constants.DATA_LIST); |
||||
Assert.assertTrue(workFlowLineageList.size()>0); |
||||
} |
||||
|
||||
@Test |
||||
public void testQueryWorkFlowLineageByIds() { |
||||
|
||||
Set<Integer> ids = new HashSet<>(); |
||||
ids.add(1); |
||||
ids.add(2); |
||||
|
||||
when(workFlowLineageMapper.queryByIds(ids, 1)).thenReturn(getWorkFlowLineages()); |
||||
when(workFlowLineageMapper.querySourceTarget(1)).thenReturn(getWorkFlowRelation()); |
||||
Map<String, Object> result = workFlowLineageService.queryWorkFlowLineageByIds(ids,1); |
||||
Map<String, Object> workFlowLists = (Map<String, Object>)result.get(Constants.DATA_LIST); |
||||
List<WorkFlowLineage> workFlowLineages = (List<WorkFlowLineage>)workFlowLists.get("workFlowList"); |
||||
List<WorkFlowRelation> workFlowRelations = (List<WorkFlowRelation>)workFlowLists.get("workFlowRelationList"); |
||||
Assert.assertTrue(workFlowLineages.size()>0); |
||||
Assert.assertTrue(workFlowRelations.size()>0); |
||||
} |
||||
|
||||
private List<WorkFlowLineage> getWorkFlowLineages() { |
||||
List<WorkFlowLineage> workFlowLineages = new ArrayList<>(); |
||||
WorkFlowLineage workFlowLineage = new WorkFlowLineage(); |
||||
workFlowLineage.setWorkFlowId(1); |
||||
workFlowLineage.setWorkFlowName("testdag"); |
||||
workFlowLineages.add(workFlowLineage); |
||||
return workFlowLineages; |
||||
} |
||||
|
||||
private List<WorkFlowRelation> getWorkFlowRelation(){ |
||||
List<WorkFlowRelation> workFlowRelations = new ArrayList<>(); |
||||
WorkFlowRelation workFlowRelation = new WorkFlowRelation(); |
||||
workFlowRelation.setSourceWorkFlowId(1); |
||||
workFlowRelation.setTargetWorkFlowId(2); |
||||
workFlowRelations.add(workFlowRelation); |
||||
return workFlowRelations; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,94 @@
|
||||
/* |
||||
* Licensed to the Apache Software Foundation (ASF) under one or more |
||||
* contributor license agreements. See the NOTICE file distributed with |
||||
* this work for additional information regarding copyright ownership. |
||||
* The ASF licenses this file to You under the Apache License, Version 2.0 |
||||
* (the "License"); you may not use this file except in compliance with |
||||
* the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package org.apache.dolphinscheduler.dao.entity; |
||||
|
||||
import java.util.Date; |
||||
|
||||
public class WorkFlowLineage { |
||||
private int workFlowId; |
||||
private String workFlowName; |
||||
private String workFlowPublishStatus; |
||||
private Date scheduleStartTime; |
||||
private Date scheduleEndTime; |
||||
private String crontab; |
||||
private int schedulePublishStatus; |
||||
private String sourceWorkFlowId; |
||||
|
||||
public String getSourceWorkFlowId() { |
||||
return sourceWorkFlowId; |
||||
} |
||||
|
||||
public void setSourceWorkFlowId(String sourceWorkFlowId) { |
||||
this.sourceWorkFlowId = sourceWorkFlowId; |
||||
} |
||||
|
||||
public int getWorkFlowId() { |
||||
return workFlowId; |
||||
} |
||||
|
||||
public void setWorkFlowId(int workFlowId) { |
||||
this.workFlowId = workFlowId; |
||||
} |
||||
|
||||
public String getWorkFlowName() { |
||||
return workFlowName; |
||||
} |
||||
|
||||
public void setWorkFlowName(String workFlowName) { |
||||
this.workFlowName = workFlowName; |
||||
} |
||||
|
||||
public String getWorkFlowPublishStatus() { |
||||
return workFlowPublishStatus; |
||||
} |
||||
|
||||
public void setWorkFlowPublishStatus(String workFlowPublishStatus) { |
||||
this.workFlowPublishStatus = workFlowPublishStatus; |
||||
} |
||||
|
||||
public Date getScheduleStartTime() { |
||||
return scheduleStartTime; |
||||
} |
||||
|
||||
public void setScheduleStartTime(Date scheduleStartTime) { |
||||
this.scheduleStartTime = scheduleStartTime; |
||||
} |
||||
|
||||
public Date getScheduleEndTime() { |
||||
return scheduleEndTime; |
||||
} |
||||
|
||||
public void setScheduleEndTime(Date scheduleEndTime) { |
||||
this.scheduleEndTime = scheduleEndTime; |
||||
} |
||||
|
||||
public String getCrontab() { |
||||
return crontab; |
||||
} |
||||
|
||||
public void setCrontab(String crontab) { |
||||
this.crontab = crontab; |
||||
} |
||||
|
||||
public int getSchedulePublishStatus() { |
||||
return schedulePublishStatus; |
||||
} |
||||
|
||||
public void setSchedulePublishStatus(int schedulePublishStatus) { |
||||
this.schedulePublishStatus = schedulePublishStatus; |
||||
} |
||||
} |
@ -0,0 +1,38 @@
|
||||
/* |
||||
* Licensed to the Apache Software Foundation (ASF) under one or more |
||||
* contributor license agreements. See the NOTICE file distributed with |
||||
* this work for additional information regarding copyright ownership. |
||||
* The ASF licenses this file to You under the Apache License, Version 2.0 |
||||
* (the "License"); you may not use this file except in compliance with |
||||
* the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package org.apache.dolphinscheduler.dao.entity; |
||||
|
||||
public class WorkFlowRelation { |
||||
private int sourceWorkFlowId; |
||||
private int targetWorkFlowId; |
||||
|
||||
public int getSourceWorkFlowId() { |
||||
return sourceWorkFlowId; |
||||
} |
||||
|
||||
public void setSourceWorkFlowId(int sourceWorkFlowId) { |
||||
this.sourceWorkFlowId = sourceWorkFlowId; |
||||
} |
||||
|
||||
public int getTargetWorkFlowId() { |
||||
return targetWorkFlowId; |
||||
} |
||||
|
||||
public void setTargetWorkFlowId(int targetWorkFlowId) { |
||||
this.targetWorkFlowId = targetWorkFlowId; |
||||
} |
||||
} |
@ -0,0 +1,32 @@
|
||||
/* |
||||
* Licensed to the Apache Software Foundation (ASF) under one or more |
||||
* contributor license agreements. See the NOTICE file distributed with |
||||
* this work for additional information regarding copyright ownership. |
||||
* The ASF licenses this file to You under the Apache License, Version 2.0 |
||||
* (the "License"); you may not use this file except in compliance with |
||||
* the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package org.apache.dolphinscheduler.dao.mapper; |
||||
|
||||
import org.apache.dolphinscheduler.dao.entity.WorkFlowLineage; |
||||
import org.apache.dolphinscheduler.dao.entity.WorkFlowRelation; |
||||
import org.apache.ibatis.annotations.Param; |
||||
import java.util.List; |
||||
import java.util.Set; |
||||
|
||||
public interface WorkFlowLineageMapper { |
||||
|
||||
public List<WorkFlowLineage> queryByName(@Param("searchVal") String searchVal, @Param("projectId") int projectId); |
||||
|
||||
public List<WorkFlowLineage> queryByIds(@Param("ids") Set<Integer> ids, @Param("projectId") int projectId); |
||||
|
||||
public List<WorkFlowRelation> querySourceTarget(@Param("id") int id); |
||||
} |
@ -0,0 +1,103 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?> |
||||
<!-- |
||||
~ Licensed to the Apache Software Foundation (ASF) under one or more |
||||
~ contributor license agreements. See the NOTICE file distributed with |
||||
~ this work for additional information regarding copyright ownership. |
||||
~ The ASF licenses this file to You under the Apache License, Version 2.0 |
||||
~ (the "License"); you may not use this file except in compliance with |
||||
~ the License. You may obtain a copy of the License at |
||||
~ |
||||
~ http://www.apache.org/licenses/LICENSE-2.0 |
||||
~ |
||||
~ Unless required by applicable law or agreed to in writing, software |
||||
~ distributed under the License is distributed on an "AS IS" BASIS, |
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
~ See the License for the specific language governing permissions and |
||||
~ limitations under the License. |
||||
--> |
||||
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > |
||||
<mapper namespace="org.apache.dolphinscheduler.dao.mapper.WorkFlowLineageMapper"> |
||||
<select id="queryByName" resultType="org.apache.dolphinscheduler.dao.entity.WorkFlowLineage"> |
||||
select tepd.id as work_flow_id,tepd.name as work_flow_name |
||||
from t_ds_process_definition tepd |
||||
left join t_ds_schedules tes on tepd.id = tes.process_definition_id |
||||
where tepd.project_id = #{projectId} |
||||
<if test="searchVal != null and searchVal != ''"> |
||||
and tepd.name like concat('%', #{searchVal}, '%') |
||||
</if> |
||||
</select> |
||||
<select id="queryByIds" resultType="org.apache.dolphinscheduler.dao.entity.WorkFlowLineage" databaseId="mysql"> |
||||
select tepd.id as work_flow_id,tepd.name as work_flow_name, |
||||
(case when json_extract(tepd.process_definition_json, '$**.dependItemList') is not null then 1 else 0 end) as is_depend_work_flow, |
||||
json_extract(tepd.process_definition_json, '$**.definitionId') as source_work_flow_id, |
||||
tepd.release_state as work_flow_publish_status, |
||||
tes.start_time as schedule_start_time, |
||||
tes.end_time as schedule_end_time, |
||||
tes.crontab as crontab, |
||||
tes.release_state as schedule_publish_status |
||||
from t_ds_process_definition tepd |
||||
left join t_ds_schedules tes on tepd.id = tes.process_definition_id |
||||
where tepd.project_id = #{projectId} |
||||
<if test="ids != null and ids.size()>0"> |
||||
and tepd.id in |
||||
<foreach collection="ids" index="index" item="i" open="(" separator="," close=")"> |
||||
#{i} |
||||
</foreach> |
||||
</if> |
||||
</select> |
||||
|
||||
<select id="queryByIds" resultType="org.apache.dolphinscheduler.dao.entity.WorkFlowLineage" databaseId="pg"> |
||||
select a.work_flow_id, |
||||
a.work_flow_name, |
||||
a.is_depend_work_flow, |
||||
array_agg(a.source_id) as source_id, |
||||
a.work_flow_publish_status, |
||||
a.schedule_start_time, |
||||
a.schedule_end_time, |
||||
a.crontab, |
||||
a.schedule_publish_status |
||||
from ( |
||||
select tepd.id as work_flow_id,tepd.name as work_flow_name, |
||||
case when tepd.process_definition_json::json#>'{tasks,1,dependence}' is not null then 1 else 0 end as is_depend_work_flow, |
||||
(json_array_elements(tepd.process_definition_json::json#>'{tasks}')#>>'{dependence,dependTaskList,0,dependItemList,0,definitionId}') as source_id, |
||||
tepd.release_state as work_flow_publish_status, |
||||
tes.start_time as schedule_start_time, |
||||
tes.end_time as schedule_end_time, |
||||
tes.crontab as crontab, |
||||
tes.release_state as schedule_publish_status |
||||
from t_ds_process_definition tepd |
||||
left join t_ds_schedules tes on tepd.id = tes.process_definition_id |
||||
where tepd.project_id = #{projectId} |
||||
<if test="ids != null and ids.size()>0"> |
||||
and tepd.id in |
||||
<foreach collection="ids" index="index" item="i" open="(" separator="," close=")"> |
||||
#{i} |
||||
</foreach> |
||||
</if> |
||||
) a |
||||
where (a.is_depend_work_flow = 1 and source_id is not null) or (a.is_depend_work_flow = 0) |
||||
group by a.work_flow_id,a.work_flow_name,a.is_depend_work_flow,a.work_flow_publish_status,a.schedule_start_time, |
||||
a.schedule_end_time,a.crontab,a.schedule_publish_status |
||||
</select> |
||||
|
||||
<select id="querySourceTarget" resultType="org.apache.dolphinscheduler.dao.entity.WorkFlowRelation" databaseId="mysql"> |
||||
select id as target_work_flow_id,#{id} as source_work_flow_id |
||||
from t_ds_process_definition t |
||||
where json_extract(t.process_definition_json, '$**.dependItemList') is not null |
||||
and find_in_set(#{id}, replace(replace(replace(json_extract(t.process_definition_json, '$**.definitionId'), '[', ''),']', ''), ' ', '')) > 0 |
||||
</select> |
||||
|
||||
<select id="querySourceTarget" resultType="org.apache.dolphinscheduler.dao.entity.WorkFlowRelation" databaseId="pg"> |
||||
select a.work_flow_id as target_work_flow_id, |
||||
a.source_id as source_work_flow_id |
||||
from ( |
||||
select tepd.id as work_flow_id, |
||||
(json_array_elements(tepd.process_definition_json::json#>'{tasks}')#>>'{dependence,dependTaskList,0,dependItemList,0,definitionId}') as source_id |
||||
from t_ds_process_definition tepd |
||||
left join t_ds_schedules tes on tepd.id = tes.process_definition_id |
||||
where tepd.project_id = 1) a |
||||
where source_id = #{id}::text; |
||||
</select> |
||||
|
||||
</mapper> |
@ -0,0 +1,62 @@
|
||||
/* |
||||
* Licensed to the Apache Software Foundation (ASF) under one or more |
||||
* contributor license agreements. See the NOTICE file distributed with |
||||
* this work for additional information regarding copyright ownership. |
||||
* The ASF licenses this file to You under the Apache License, Version 2.0 |
||||
* (the "License"); you may not use this file except in compliance with |
||||
* the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package org.apache.dolphinscheduler.dao.mapper; |
||||
|
||||
import org.apache.dolphinscheduler.dao.entity.WorkFlowLineage; |
||||
import org.apache.dolphinscheduler.dao.entity.WorkFlowRelation; |
||||
import org.junit.Assert; |
||||
import org.junit.Test; |
||||
import org.junit.runner.RunWith; |
||||
import org.springframework.beans.factory.annotation.Autowired; |
||||
import org.springframework.boot.test.context.SpringBootTest; |
||||
import org.springframework.test.annotation.Rollback; |
||||
import org.springframework.test.context.junit4.SpringRunner; |
||||
import org.springframework.transaction.annotation.Transactional; |
||||
|
||||
import java.util.HashSet; |
||||
import java.util.List; |
||||
import java.util.Set; |
||||
|
||||
@RunWith(SpringRunner.class) |
||||
@SpringBootTest |
||||
@Transactional |
||||
@Rollback(true) |
||||
public class WorkFlowLineageMapperTest { |
||||
@Autowired |
||||
private WorkFlowLineageMapper workFlowLineageMapper; |
||||
|
||||
@Test |
||||
public void testQueryByName() { |
||||
List<WorkFlowLineage> workFlowLineages = workFlowLineageMapper.queryByName("test",1); |
||||
Assert.assertNotEquals(workFlowLineages.size(), 0); |
||||
} |
||||
|
||||
|
||||
@Test |
||||
public void testQueryByIds() { |
||||
Set<Integer> ids = new HashSet<>(); |
||||
ids.add(1); |
||||
List<WorkFlowLineage> workFlowLineages = workFlowLineageMapper.queryByIds(ids,1); |
||||
Assert.assertNotEquals(workFlowLineages.size(), 0); |
||||
} |
||||
|
||||
@Test |
||||
public void testQuerySourceTarget() { |
||||
List<WorkFlowRelation> workFlowRelations = workFlowLineageMapper.querySourceTarget(1); |
||||
Assert.assertNotEquals(workFlowRelations.size(), 0); |
||||
} |
||||
} |
@ -0,0 +1,467 @@
|
||||
/* |
||||
* Licensed to the Apache Software Foundation (ASF) under one or more |
||||
* contributor license agreements. See the NOTICE file distributed with |
||||
* this work for additional information regarding copyright ownership. |
||||
* The ASF licenses this file to You under the Apache License, Version 2.0 |
||||
* (the "License"); you may not use this file except in compliance with |
||||
* the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0 |
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
<template> |
||||
<div class="shell-model"> |
||||
<!--deploy mode--> |
||||
<div class="list-box-4p"> |
||||
<div class="clearfix list"> |
||||
<span class="sp1">{{$t('Deploy Mode')}}</span> |
||||
<span class="sp2"> |
||||
<x-radio-group v-model="deployMode"> |
||||
<x-radio :label="'client'" :disabled="isDetails"></x-radio> |
||||
<x-radio :label="'cluster'" :disabled="isDetails"></x-radio> |
||||
<x-radio :label="'local'" :disabled="isDetails"></x-radio> |
||||
</x-radio-group> |
||||
</span> |
||||
<span class="sp1 sp3">{{$t('Queue')}}</span> |
||||
<span class="sp4"> |
||||
<x-input |
||||
:disabled="isDetails" |
||||
type="input" |
||||
v-model="queue" |
||||
:placeholder="$t('Please enter queue value')" |
||||
style="width: 60%;" |
||||
autocomplete="off"> |
||||
</x-input> |
||||
</span> |
||||
</div> |
||||
</div> |
||||
<!--master--> |
||||
<div class="list-box-4p" v-if="deployMode !== 'local'"> |
||||
<div class="clearfix list"> |
||||
<span class="sp1">{{$t('Master')}}</span> |
||||
<span class="sp4"> |
||||
<x-select |
||||
style="width: 130px;" |
||||
v-model="master" |
||||
:disabled="isDetails"> |
||||
<x-option |
||||
v-for="city in masterType" |
||||
:key="city.code" |
||||
:value="city.code" |
||||
:label="city.code"> |
||||
</x-option> |
||||
</x-select> |
||||
</span> |
||||
<span v-if="masterUrlState"> |
||||
<x-input |
||||
:disabled="isDetails" |
||||
type="input" |
||||
v-model="masterUrl" |
||||
:placeholder="$t('Please Enter Url')" |
||||
style="width: 60%;" |
||||
autocomplete="off"> |
||||
</x-input> |
||||
</span> |
||||
</div> |
||||
</div> |
||||
<!--config file--> |
||||
<m-list-box> |
||||
<div slot="text">{{$t('Resources')}}</div> |
||||
<div slot="content"> |
||||
<treeselect v-model="resourceList" :disable-branch-nodes="true" :multiple="true" :options="options" :normalizer="normalizer" :disabled="isDetails" :value-consists-of="valueConsistsOf" :placeholder="$t('Please select resources')"> |
||||
<div slot="value-label" slot-scope="{ node }">{{ node.raw.fullName }}</div> |
||||
</treeselect> |
||||
</div> |
||||
</m-list-box> |
||||
<!--custom parameters--> |
||||
<m-list-box> |
||||
<div slot="text">{{$t('Custom Parameters')}}</div> |
||||
<div slot="content"> |
||||
<m-local-params |
||||
ref="refLocalParams" |
||||
@on-local-params="_onLocalParams" |
||||
:udp-list="localParams" |
||||
:hide="false"> |
||||
</m-local-params> |
||||
</div> |
||||
</m-list-box> |
||||
</div> |
||||
</template> |
||||
<script> |
||||
import _ from 'lodash' |
||||
import i18n from '@/module/i18n' |
||||
import mListBox from './_source/listBox' |
||||
import mScriptBox from './_source/scriptBox' |
||||
import mResources from './_source/resources' |
||||
import mLocalParams from './_source/localParams' |
||||
import disabledState from '@/module/mixin/disabledState' |
||||
import Treeselect from '@riophae/vue-treeselect' |
||||
import '@riophae/vue-treeselect/dist/vue-treeselect.css' |
||||
|
||||
export default { |
||||
name: 'waterdrop', |
||||
data () { |
||||
return { |
||||
valueConsistsOf: 'LEAF_PRIORITY', |
||||
// script |
||||
rawScript: '', |
||||
// waterdrop script |
||||
baseScript: 'sh ${WATERDROP_HOME}/bin/start-waterdrop.sh', |
||||
// resourceNameVal |
||||
resourceNameVal : [], |
||||
// Custom parameter |
||||
localParams: [], |
||||
// resource(list) |
||||
resourceList: [], |
||||
// Deployment method |
||||
deployMode: 'client', |
||||
// Deployment master |
||||
queue: 'default', |
||||
// Deployment master |
||||
master: 'yarn', |
||||
// Spark version(LIst) |
||||
masterType: [{ code: 'yarn' }, { code: 'local' }, { code: 'spark://' }, { code: 'mesos://' }], |
||||
// Deployment masterUrl state |
||||
masterUrlState:false, |
||||
// Deployment masterUrl |
||||
masterUrl: '', |
||||
// Cache ResourceList |
||||
cacheResourceList: [], |
||||
// define options |
||||
options: [], |
||||
normalizer(node) { |
||||
return { |
||||
label: node.name |
||||
} |
||||
}, |
||||
allNoResources: [], |
||||
noRes: [] |
||||
} |
||||
}, |
||||
mixins: [disabledState], |
||||
props: { |
||||
backfillItem: Object |
||||
}, |
||||
methods: { |
||||
/** |
||||
* return localParams |
||||
*/ |
||||
_onLocalParams (a) { |
||||
this.localParams = a |
||||
}, |
||||
/** |
||||
* return resourceList |
||||
* |
||||
*/ |
||||
_onResourcesData (a) { |
||||
this.resourceList = a |
||||
}, |
||||
/** |
||||
* cache resourceList |
||||
*/ |
||||
_onCacheResourcesData (a) { |
||||
this.cacheResourceList = a |
||||
}, |
||||
/** |
||||
* verification |
||||
*/ |
||||
_verification () { |
||||
// localParams Subcomponent verification |
||||
if (!this.$refs.refLocalParams._verifProp()) { |
||||
return false |
||||
} |
||||
// noRes |
||||
if (this.noRes.length>0) { |
||||
this.$message.warning(`${i18n.$t('Please delete all non-existent resources')}`) |
||||
return false |
||||
} |
||||
// noRes |
||||
if (!this.resourceNameVal.resourceList) { |
||||
this.$message.warning(`${i18n.$t('Please select the waterdrop resources')}`) |
||||
return false |
||||
} |
||||
if (this.resourceNameVal.resourceList && this.resourceNameVal.resourceList.length==0) { |
||||
this.$message.warning(`${i18n.$t('Please select the waterdrop resources')}`) |
||||
return false |
||||
} |
||||
// Process resourcelist |
||||
let dataProcessing= _.map(this.resourceList, v => { |
||||
return { |
||||
id: v |
||||
} |
||||
}) |
||||
//verify deploy mode |
||||
let deployMode = this.deployMode |
||||
let master = this.master |
||||
let masterUrl = this.masterUrl |
||||
|
||||
if(this.deployMode == 'local'){ |
||||
master = 'local' |
||||
masterUrl = '' |
||||
deployMode = 'client' |
||||
} |
||||
// get local params |
||||
let locparams = '' |
||||
this.localParams.forEach(v=>{ |
||||
locparams = locparams + ' --variable ' + v.prop + '=' + v.value |
||||
} |
||||
) |
||||
// get waterdrop script |
||||
let tureScript = '' |
||||
this.resourceNameVal.resourceList.forEach(v=>{ |
||||
tureScript = tureScript + this.baseScript + |
||||
' --master '+ master + masterUrl + |
||||
' --deploy-mode '+ deployMode + |
||||
' --queue '+ this.queue + |
||||
' --config ' + v.res + |
||||
locparams + ' \n' |
||||
}) |
||||
|
||||
// storage |
||||
this.$emit('on-params', { |
||||
resourceList: dataProcessing, |
||||
localParams: this.localParams, |
||||
rawScript: tureScript, |
||||
}) |
||||
|
||||
return true |
||||
}, |
||||
diGuiTree(item) { // Recursive convenience tree structure |
||||
item.forEach(item => { |
||||
item.children === '' || item.children === undefined || item.children === null || item.children.length === 0? |
||||
this.operationTree(item) : this.diGuiTree(item.children); |
||||
}) |
||||
}, |
||||
operationTree(item) { |
||||
if(item.dirctory) { |
||||
item.isDisabled =true |
||||
} |
||||
delete item.children |
||||
}, |
||||
searchTree(element, id) { |
||||
// 根据id查找节点 |
||||
if (element.id == id) { |
||||
return element; |
||||
} else if (element.children != null) { |
||||
var i; |
||||
var result = null; |
||||
for (i = 0; result == null && i < element.children.length; i++) { |
||||
result = this.searchTree(element.children[i], id); |
||||
} |
||||
return result; |
||||
} |
||||
return null; |
||||
}, |
||||
dataProcess(backResource) { |
||||
let isResourceId = [] |
||||
let resourceIdArr = [] |
||||
if(this.resourceList.length>0) { |
||||
this.resourceList.forEach(v=>{ |
||||
this.options.forEach(v1=>{ |
||||
if(this.searchTree(v1,v)) { |
||||
isResourceId.push(this.searchTree(v1,v)) |
||||
} |
||||
}) |
||||
}) |
||||
resourceIdArr = isResourceId.map(item=>{ |
||||
return item.id |
||||
}) |
||||
Array.prototype.diff = function(a) { |
||||
return this.filter(function(i) {return a.indexOf(i) < 0;}); |
||||
}; |
||||
let diffSet = this.resourceList.diff(resourceIdArr); |
||||
let optionsCmp = [] |
||||
if(diffSet.length>0) { |
||||
diffSet.forEach(item=>{ |
||||
backResource.forEach(item1=>{ |
||||
if(item==item1.id || item==item1.res) { |
||||
optionsCmp.push(item1) |
||||
} |
||||
}) |
||||
}) |
||||
} |
||||
let noResources = [{ |
||||
id: -1, |
||||
name: $t('Unauthorized or deleted resources'), |
||||
fullName: '/'+$t('Unauthorized or deleted resources'), |
||||
children: [] |
||||
}] |
||||
if(optionsCmp.length>0) { |
||||
this.allNoResources = optionsCmp |
||||
optionsCmp = optionsCmp.map(item=>{ |
||||
return {id: item.id,name: item.name,fullName: item.res} |
||||
}) |
||||
optionsCmp.forEach(item=>{ |
||||
item.isNew = true |
||||
}) |
||||
noResources[0].children = optionsCmp |
||||
this.options = this.options.concat(noResources) |
||||
} |
||||
} |
||||
} |
||||
}, |
||||
watch: { |
||||
//Watch the cacheParams |
||||
cacheParams (val) { |
||||
this.resourceNameVal = val |
||||
this.$emit('on-cache-params', val); |
||||
}, |
||||
"master": { |
||||
handler(code) { |
||||
if(code == 'spark://'){ |
||||
this.masterUrlState = true; |
||||
}else if(code == 'mesos://'){ |
||||
this.masterUrlState = true; |
||||
}else{ |
||||
this.masterUrlState = false; |
||||
this.masterUrl = '' |
||||
} |
||||
} |
||||
}, |
||||
}, |
||||
computed: { |
||||
cacheParams () { |
||||
let isResourceId = [] |
||||
let resourceIdArr = [] |
||||
if(this.resourceList.length>0) { |
||||
this.resourceList.forEach(v=>{ |
||||
this.options.forEach(v1=>{ |
||||
if(this.searchTree(v1,v)) { |
||||
isResourceId.push(this.searchTree(v1,v)) |
||||
} |
||||
}) |
||||
}) |
||||
resourceIdArr = isResourceId.map(item=>{ |
||||
return {id: item.id,name: item.name,res: item.fullName} |
||||
}) |
||||
} |
||||
let result = [] |
||||
resourceIdArr.forEach(item=>{ |
||||
this.allNoResources.forEach(item1=>{ |
||||
if(item.id==item1.id) { |
||||
// resultBool = true |
||||
result.push(item1) |
||||
} |
||||
}) |
||||
}) |
||||
this.noRes = result |
||||
return { |
||||
resourceList: resourceIdArr, |
||||
localParams: this.localParams, |
||||
deployMode: this.deployMode, |
||||
master: this.master, |
||||
masterUrl: this.masterUrl, |
||||
queue:this.queue, |
||||
} |
||||
} |
||||
}, |
||||
created () { |
||||
let item = this.store.state.dag.resourcesListS |
||||
this.diGuiTree(item) |
||||
this.options = item |
||||
let o = this.backfillItem |
||||
|
||||
// Non-null objects represent backfill |
||||
if (!_.isEmpty(o)) { |
||||
this.master = o.params.master || 'yarn' |
||||
this.deployMode = o.params.deployMode || 'client' |
||||
this.masterUrl = o.params.masterUrl || '' |
||||
this.queue = o.params.queue || 'default' |
||||
this.rawScript = o.params.rawScript || '' |
||||
|
||||
// backfill resourceList |
||||
let backResource = o.params.resourceList || [] |
||||
let resourceList = o.params.resourceList || [] |
||||
if (resourceList.length) { |
||||
_.map(resourceList, v => { |
||||
if(!v.id) { |
||||
this.store.dispatch('dag/getResourceId',{ |
||||
type: 'FILE', |
||||
fullName: '/'+v.res |
||||
}).then(res => { |
||||
this.resourceList.push(res.id) |
||||
this.dataProcess(backResource) |
||||
}).catch(e => { |
||||
this.resourceList.push(v.res) |
||||
this.dataProcess(backResource) |
||||
}) |
||||
} else { |
||||
this.resourceList.push(v.id) |
||||
this.dataProcess(backResource) |
||||
} |
||||
}) |
||||
} |
||||
// backfill localParams |
||||
let localParams = o.params.localParams || [] |
||||
if (localParams.length) { |
||||
this.localParams = localParams |
||||
} |
||||
} |
||||
}, |
||||
mounted () { |
||||
}, |
||||
destroyed () { |
||||
}, |
||||
components: { mLocalParams, mListBox, mResources, mScriptBox, Treeselect } |
||||
} |
||||
</script> |
||||
<style lang="scss" rel="stylesheet/scss" scope> |
||||
.scriptModal { |
||||
.ans-modal-box-content-wrapper { |
||||
width: 90%; |
||||
.ans-modal-box-close { |
||||
right: -12px; |
||||
top: -16px; |
||||
color: #fff; |
||||
} |
||||
} |
||||
} |
||||
.ans-modal-box-close { |
||||
z-index: 100; |
||||
} |
||||
.ans-modal-box-max { |
||||
position: absolute; |
||||
right: -12px; |
||||
top: -16px; |
||||
} |
||||
.vue-treeselect--disabled { |
||||
.vue-treeselect__control { |
||||
background-color: #ecf3f8; |
||||
.vue-treeselect__single-value { |
||||
color: #6d859e; |
||||
} |
||||
} |
||||
} |
||||
.list-box-4p { |
||||
.list { |
||||
margin-bottom: 14px; |
||||
.sp1 { |
||||
float: left; |
||||
width: 112px; |
||||
text-align: right; |
||||
margin-right: 10px; |
||||
font-size: 14px; |
||||
color: #777; |
||||
display: inline-block; |
||||
padding-top: 6px; |
||||
} |
||||
.sp2 { |
||||
float: left; |
||||
margin-right: 4px; |
||||
padding-top: 6px; |
||||
} |
||||
.sp3 { |
||||
width: 90px; |
||||
} |
||||
.sp4 { |
||||
float: left; |
||||
margin-right: 4px; |
||||
} |
||||
} |
||||
} |
||||
</style> |
After Width: | Height: | Size: 1.6 KiB |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue