Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ workflows:
- test
filters:
branches:
only: ['dev', 'dev-msinteg']
only: ['dev', 'feature/metadata-management']
- deployTest02:
requires:
- test
Expand Down
6 changes: 6 additions & 0 deletions config/webpack/production.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
const path = require('path')
const webpackMerge = require('webpack-merge')
const CompressionPlugin = require('compression-webpack-plugin')
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')

const dirname = path.resolve(__dirname, '../..')

Expand Down Expand Up @@ -52,5 +53,10 @@ combinedConfig.plugins.push(
minRatio: 0.8
})
)
combinedConfig.plugins = combinedConfig.plugins.map((plugin) => {
// console.log(/UglifyJsPlugin/.test(plugin.constructor.toString()))
if (/UglifyJsPlugin/.test(plugin.constructor.toString())) return new UglifyJsPlugin()
return plugin
})

module.exports = combinedConfig
3 changes: 3 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@
"stylelint": "^8.4.0",
"stylelint-config-standard": "^18.0.0",
"to-ico": "1.1.2",
"uglifyjs-webpack-plugin": "^1.1.5",
"watch-run": "^1.2.4",
"webpack": "^3.10.0",
"webpack-dev-server": "^2.9.1",
Expand Down Expand Up @@ -118,6 +119,8 @@
"react-dotdotdot": "^1.0.4",
"react-gateway": "^3.0.0",
"react-infinite-scroller": "^1.1.1",
"react-json-editor-ajrm": "^2.5.9",
"react-json-view": "^1.19.1",
"react-layout-pane": "^0.1.16",
"react-modal": "^1.9.7",
"react-redux": "^4.4.5",
Expand Down
185 changes: 183 additions & 2 deletions src/actions/templates.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,24 @@
/**
* Project and product templates actions
*/
import { LOAD_PROJECTS_METADATA } from '../config/constants'
import { getProjectsMetadata } from '../api/templates'

import _ from 'lodash'
import {
LOAD_PROJECTS_METADATA, ADD_PROJECTS_METADATA, UPDATE_PROJECTS_METADATA, REMOVE_PROJECTS_METADATA,
PROJECT_TEMPLATES_SORT, PRODUCT_TEMPLATES_SORT, PROJECT_TYPES_SORT, CREATE_PROJECT_TEMPLATE, CREATE_PROJECT_TYPE,
CREATE_PRODUCT_TEMPLATE,
PRODUCT_CATEGORIES_SORT,
CREATE_PRODUCT_CATEGORY,
REMOVE_PROJECT_TYPE,
REMOVE_PRODUCT_CATEGORY,
REMOVE_PROJECT_TEMPLATE,
REMOVE_PRODUCT_TEMPLATE
} from '../config/constants'
import {
getProjectsMetadata,
createProjectsMetadata as createProjectsMetadataAPI,
updateProjectsMetadata as updateProjectsMetadataAPI,
deleteProjectsMetadata as deleteProjectsMetadataAPI } from '../api/templates'

export function loadProjectsMetadata() {
return (dispatch) => {
Expand All @@ -12,3 +28,168 @@ export function loadProjectsMetadata() {
})
}
}

export function getProductTemplate() {
return (dispatch) => {
return dispatch({
type: LOAD_PROJECTS_METADATA,
payload: getProjectsMetadata()
})
}
}

export function saveProductTemplate() {
return (dispatch) => {
return dispatch({
type: LOAD_PROJECTS_METADATA,
payload: getProjectsMetadata()
})
}
}

export function createProjectsMetadata(type, data) {
return (dispatch) => {
return dispatch({
type: ADD_PROJECTS_METADATA,
payload: createProjectsMetadataAPI(type, data)
})
}
}

export function createProjectTemplate(data) {
return (dispatch) => {
return dispatch({
type: CREATE_PROJECT_TEMPLATE,
payload: createProjectsMetadataAPI('projectTemplates', data)
})
}
}

export function createProductTemplate(data) {
return (dispatch) => {
return dispatch({
type: CREATE_PRODUCT_TEMPLATE,
payload: createProjectsMetadataAPI('productTemplates', data)
})
}
}

export function createProjectType(data) {
return (dispatch) => {
return dispatch({
type: CREATE_PROJECT_TYPE,
payload: createProjectsMetadataAPI('projectTypes', data)
})
}
}

export function createProductCategory(data) {
return (dispatch) => {
return dispatch({
type: CREATE_PRODUCT_CATEGORY,
payload: createProjectsMetadataAPI('productCategories', data)
})
}
}

export function updateProjectsMetadata(metadataId, type, data) {
return (dispatch) => {
return dispatch({
type: UPDATE_PROJECTS_METADATA,
payload: updateProjectsMetadataAPI(metadataId, type, data)
})
}
}

export function deleteProjectsMetadata(metadataId, type) {
return (dispatch) => {
return dispatch({
type: REMOVE_PROJECTS_METADATA,
payload: deleteProjectsMetadataAPI(metadataId, type)
})
}
}

export function deleteProjectTemplate(metadataId) {
return (dispatch) => {
return dispatch({
type: REMOVE_PROJECT_TEMPLATE,
payload: deleteProjectsMetadataAPI(metadataId, 'projectTemplates')
})
}
}

export function deleteProjectType(metadataId) {
return (dispatch) => {
return dispatch({
type: REMOVE_PROJECT_TYPE,
payload: deleteProjectsMetadataAPI(metadataId, 'projectTypes')
})
}
}

export function deleteProductTemplate(metadataId) {
return (dispatch) => {
return dispatch({
type: REMOVE_PRODUCT_TEMPLATE,
payload: deleteProjectsMetadataAPI(metadataId, 'productTemplates')
})
}
}

export function deleteProductCategory(metadataId) {
return (dispatch) => {
return dispatch({
type: REMOVE_PRODUCT_CATEGORY,
payload: deleteProjectsMetadataAPI(metadataId, 'productCategories')
})
}
}

export function sortProjectTemplates(criteria) {
return (dispatch) => {
const fieldName = _.split(criteria, ' ')[0]
const order = _.split(criteria, ' ')[1] || 'asc'

return dispatch({
type: PROJECT_TEMPLATES_SORT,
payload: { fieldName, order }
})
}
}

export function sortProductTemplates(criteria) {
return (dispatch) => {
const fieldName = _.split(criteria, ' ')[0]
const order = _.split(criteria, ' ')[1] || 'asc'

return dispatch({
type: PRODUCT_TEMPLATES_SORT,
payload: { fieldName, order }
})
}
}

export function sortProjectTypes(criteria) {
return (dispatch) => {
const fieldName = _.split(criteria, ' ')[0]
const order = _.split(criteria, ' ')[1] || 'asc'

return dispatch({
type: PROJECT_TYPES_SORT,
payload: { fieldName, order }
})
}
}

export function sortProductCategories(criteria) {
return (dispatch) => {
const fieldName = _.split(criteria, ' ')[0]
const order = _.split(criteria, ' ')[1] || 'asc'

return dispatch({
type: PRODUCT_CATEGORIES_SORT,
payload: { fieldName, order }
})
}
}
43 changes: 43 additions & 0 deletions src/api/templates.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,46 @@ export function getProjectsMetadata() {
return axios.get(`${TC_API_URL}/v4/projects/metadata`)
.then(resp => _.get(resp.data, 'result.content', {}))
}

/**
* Create Project Metadata
* @param type The type of metadata
* @param data The data of metadata
* @returns {Promise} response body
*/
export function createProjectsMetadata(type, data) {
const path = type !== 'milestoneTemplates' ? 'projects' : 'timelines'
return axios.post(`${TC_API_URL}/v4/${path}/metadata/${type}`, {
param: data
})
.then(resp => _.get(resp.data, 'result.content', {}))
}

/**
* Update Project Metadata
* @param metadataId The primary key of metadata
* @param type The type of metadata
* @param data The data of metadata
* @returns {Promise} response body
*/
export function updateProjectsMetadata(metadataId, type, data) {
const path = type !== 'milestoneTemplates' ? 'projects' : 'timelines'
return axios.patch(`${TC_API_URL}/v4/${path}/metadata/${type}/${metadataId}`, {
param: data
})
.then(resp => _.get(resp.data, 'result.content', {}))
}

/**
* Delete Project Metadata
* @param metadataId The primary key of metadata
* @param type The type of metadata
* @returns {Promise} response body
*/
export function deleteProjectsMetadata(metadataId, type) {
const path = type !== 'milestoneTemplates' ? 'projects' : 'timelines'
return axios.delete(`${TC_API_URL}/v4/${path}/metadata/${type}/${metadataId}`)
.then(() => {
return { metadataId, type }
})
}
18 changes: 11 additions & 7 deletions src/components/Grid/GridView.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,19 @@ import Placeholder from './Placeholder'
import InfiniteScroll from 'react-infinite-scroller'
import LoadingIndicator from '../../components/LoadingIndicator/LoadingIndicator'
import NewProjectCard from '../../projects/components/projectsCard/NewProjectCard'
import { PROJECTS_LIST_PER_PAGE } from '../../config/constants'
import './GridView.scss'


const GridView = props => {
const { columns, sortHandler, currentSortField, ListComponent, resultSet, onPageChange, projectsStatus,
totalCount, pageSize, currentPageNum, infiniteScroll, infiniteAutoload, isLoading, setInfiniteAutoload, applyFilters } = props
const { columns, sortHandler, currentSortField, ListComponent, resultSet, onPageChange, noMoreResultsMessage,
totalCount, pageSize, currentPageNum, infiniteScroll, infiniteAutoload, isLoading, setInfiniteAutoload,
applyFilters, entityNamePlural,
// entityName
} = props
const paginationProps = { totalCount, pageSize, currentPageNum, onPageChange }
const headerProps = { columns, sortHandler, currentSortField }
let noMoreResultsMsg = noMoreResultsMessage
noMoreResultsMsg = noMoreResultsMsg ? noMoreResultsMsg : `No more ${entityNamePlural}`

const renderItem = (item, index) => {
return item.isPlaceholder ? <Placeholder columns={columns} key={`placeholder-${index}`} /> : <ListComponent columns={columns} item={item} key={item.id}/>
Expand Down Expand Up @@ -50,10 +54,10 @@ const GridView = props => {
)

const renderGridWithInfiniteScroll = () => {
const hasMore = currentPageNum * PROJECTS_LIST_PER_PAGE < totalCount
const hasMore = currentPageNum * pageSize < totalCount
const placeholders = []
if (isLoading & hasMore) {
for (let i = 0; i < PROJECTS_LIST_PER_PAGE; i++) {
for (let i = 0; i < pageSize; i++) {
placeholders.push({ isPlaceholder: true })
}
}
Expand All @@ -78,10 +82,10 @@ const GridView = props => {
{ false && isLoading && <LoadingIndicator /> }
{ !isLoading && !infiniteAutoload && hasMore &&
<div className="gridview-load-more">
<button type="button" className="tc-btn tc-btn-primary" onClick={handleLoadMore} key="loadMore">Load more projects</button>
<button type="button" className="tc-btn tc-btn-primary" onClick={handleLoadMore} key="loadMore">Load more {entityNamePlural}</button>
</div>
}
{ !isLoading && !hasMore && <div key="end" className="gridview-no-more">No more {projectsStatus} projects</div>}
{ !isLoading && !hasMore && <div key="end" className="gridview-no-more">{noMoreResultsMsg}</div>}
<div className="project-card project-card-new"><NewProjectCard /></div>
</div>
)
Expand Down
4 changes: 2 additions & 2 deletions src/components/Grid/GridView.scss
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,8 @@
&.icon-up,
&.icon-down {
color: $tc-black;
position: relative;
padding: 0 20px 0 0;
// position: relative;
// padding: 0 20px 0 0;
}

&.icon-up::after,
Expand Down
12 changes: 5 additions & 7 deletions src/components/Grid/ListHeader.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,18 @@ import Up from '../../assets/icons/arrow-up-big.svg'
const HeaderItem = ({item, onItemClick, currentSortField}) => {
const _onClick = () => onItemClick(item.id)
const divClasses = `flex-item-title ${item.classes}`
const desc = currentSortField.indexOf(`${item.id} desc`) > -1
const sortClasses = cn('sort-txt', {
'icon-down': currentSortField.indexOf(`${item.id} desc`) > -1,
'icon-up': currentSortField.indexOf(item.id) > -1
'icon-down': desc,
'icon-up': !desc
})
return (
<div className={divClasses}>
<div className="spacing">
{ item.sortable ?
<a href="javascript:" className={sortClasses} onClick={_onClick}>
if (sortClasses === 'icon-up') {
<Up className={sortClasses}/>
} else {
<Down className={sortClasses}/>
}
{ !desc && <Up className={sortClasses}/> }
{ !!desc && <Down className={sortClasses}/> }
{item.headerLabel}
</a>
: item.headerLabel
Expand Down
Loading