Skip to content

Commit

Permalink
Add to planning refactoring (#1907)
Browse files Browse the repository at this point in the history
  • Loading branch information
thecalcc authored Oct 11, 2024
1 parent 486497b commit 47a308c
Show file tree
Hide file tree
Showing 9 changed files with 161 additions and 114 deletions.
4 changes: 2 additions & 2 deletions client/actions/assignments/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import * as actions from '../';
import {ASSIGNMENTS, ALL_DESKS, SORT_DIRECTION} from '../../constants';
import planningUtils from '../../utils/planning';
import {getErrorMessage, isExistingItem, gettext} from '../../utils';
import planning from '../planning';
import planningActions from '../planning/api';
import {assignmentsViewRequiresArchiveItems} from '../../components/Assignments/AssignmentItem/fields';

const setBaseQuery = ({must = []}) => ({
Expand Down Expand Up @@ -415,7 +415,7 @@ const receiveAssignmentHistory = (items) => ({
* @param {object} assignment - The Assignment to load items for
*/
const loadPlanningAndEvent = (assignment) => (dispatch) =>
dispatch(planning.api.fetchById(assignment.planning_item));
dispatch(planningActions.fetchById(assignment.planning_item));

/**
* Loads the Archive items that are linked to the provided Assignment list
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import {
import {superdeskApi} from '../../../superdeskApi';
import * as selectors from '../../../selectors';
import * as actions from '../../../actions';
import planningActions from '../../../actions/planning/api';

import {assignmentUtils, eventUtils, planningUtils, getFileDownloadURL} from '../../../utils';
import {ASSIGNMENTS, WORKSPACE} from '../../../constants';

Expand Down Expand Up @@ -262,7 +264,7 @@ const mapDispatchToProps = (dispatch) => ({
removeAssignment: (assignment) => dispatch(actions.assignments.ui.showRemoveAssignmentModal(assignment)),
openArchivePreview: (assignment) => dispatch(actions.assignments.ui.openArchivePreview(assignment)),
fetchEventFiles: (event) => dispatch(actions.events.api.fetchEventFiles(event)),
fetchPlanningFiles: (planning) => dispatch(actions.planning.api.fetchPlanningFiles(planning)),
fetchPlanningFiles: (planning) => dispatch(planningActions.fetchPlanningFiles(planning)),
});

export const AssignmentPreviewContainer = connect<IStateProps, IDispatchProps>(
Expand Down
7 changes: 4 additions & 3 deletions client/components/Planning/PlanningEditor/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {planningUtils, eventUtils, lockUtils} from '../../../utils';
import {EditorForm} from '../../Editor/EditorForm';
import {PlanningEditorHeader} from './PlanningEditorHeader';
import {COVERAGES} from '../../../constants';
import planningActions from '../../../actions/planning/api';

interface IProps {
original?: IPlanningItem;
Expand Down Expand Up @@ -97,9 +98,9 @@ const mapStateToProps = (state) => ({

const mapDispatchToProps = (dispatch) => ({
fetchEventFiles: (event) => dispatch(actions.events.api.fetchEventFiles(event)),
fetchPlanningFiles: (planning) => dispatch(actions.planning.api.fetchPlanningFiles(planning)),
uploadFiles: (files) => dispatch(actions.planning.api.uploadFiles({files: files})),
removeFile: (file) => dispatch(actions.planning.api.removeFile(file)),
fetchPlanningFiles: (planning) => dispatch(planningActions.fetchPlanningFiles(planning)),
uploadFiles: (files) => dispatch(planningActions.uploadFiles({files: files})),
removeFile: (file) => dispatch(planningActions.removeFile(file)),
setCoverageDefaultDesk: (coverage) => dispatch(actions.users.setCoverageDefaultDesk(coverage)),
setCoverageAddAdvancedMode: (advancedMode) => dispatch(actions.users.setCoverageAddAdvancedMode(advancedMode)),
});
Expand Down
3 changes: 2 additions & 1 deletion client/components/Planning/PlanningPreviewContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {eventUtils, getCreator, getFileDownloadURL} from '../../utils';
import {getUserInterfaceLanguageFromCV} from '../../utils/users';
import * as selectors from '../../selectors';
import * as actions from '../../actions';
import planningActions from '../../actions/planning/api';

import {
AuditInformation,
Expand Down Expand Up @@ -71,7 +72,7 @@ const mapStateToProps = (state, ownProps) => ({
const mapDispatchToProps = (dispatch) => ({
onEditEvent: (event) => dispatch(actions.main.openForEdit(event)),
fetchEventFiles: (event) => dispatch(actions.events.api.fetchEventFiles(event)),
fetchPlanningFiles: (planning) => dispatch(actions.planning.api.fetchPlanningFiles(planning)),
fetchPlanningFiles: (planning) => dispatch(planningActions.fetchPlanningFiles(planning)),
});

export class PlanningPreviewContentComponent extends React.PureComponent<IProps> {
Expand Down
33 changes: 26 additions & 7 deletions client/controllers/AddToPlanningController.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@ import ReactDOM from 'react-dom';
import {Provider} from 'react-redux';
import {ModalsContainer} from '../components';
import {planning} from '../actions';
import {get, isEmpty, isNumber} from 'lodash';
import {get, isEmpty, isNumber, noop} from 'lodash';
import {registerNotifications, getErrorMessage, isExistingItem} from '../utils';
import {WORKSPACE, MODALS} from '../constants';
import {GET_LABEL_MAP} from 'superdesk-core/scripts/apps/workspace/content/constants';
import {IArticle, IContentProfile} from 'superdesk-api';
import {authoringReactViewEnabled} from 'appConfig';
import {planningApi, superdeskApi} from '../superdeskApi';
import {PLANNING_VIEW} from '../interfaces';

Expand Down Expand Up @@ -87,7 +88,7 @@ export class AddToPlanningController {
return sdPlanningStore.initWorkspace(WORKSPACE.AUTHORING, this.loadWorkspace)
.then(
this.render,
this.$scope.resolve
!authoringReactViewEnabled ? this.$scope.resolve : noop
);
}

Expand Down Expand Up @@ -190,7 +191,9 @@ export class AddToPlanningController {
this.store.dispatch(actions.resetStore());

if (this.superdeskFlags.flags.authoring || !this.rendered) {
this.$scope.resolve();
if (!authoringReactViewEnabled) {
this.$scope.resolve();
}
return;
}

Expand All @@ -205,7 +208,9 @@ export class AddToPlanningController {
body: this.gettext('The item was unlocked by "{{ username }}"', {username}),
action: () => {
this.newsItem.lock_session = null;
this.$scope.resolve();
if (!authoringReactViewEnabled) {
this.$scope.resolve();
}
},
},
})));
Expand Down Expand Up @@ -263,15 +268,19 @@ export class AddToPlanningController {
this.notify.error(err);
});

this.$scope.resolve('foo');
if (!authoringReactViewEnabled) {
this.$scope.resolve('foo');
}
return Promise.reject('foo');
}

if (this.lock.isLocked(newsItem)) {
this.notify.error(
this.gettext('Item already locked.')
);
this.$scope.resolve('bar');
if (!authoringReactViewEnabled) {
this.$scope.resolve('bar');
}
return Promise.reject('bar');
}

Expand All @@ -284,13 +293,23 @@ export class AddToPlanningController {
this.notify.error(
getErrorMessage(error, this.gettext('Failed to lock the item.'))
);
this.$scope.resolve(error);
if (!authoringReactViewEnabled) {
this.$scope.resolve(error);
}
return Promise.reject(error);
}
);
}

return Promise.resolve(newsItem);
}, (error) => {
this.notify.error(
getErrorMessage(error, this.gettext('Failed to load the item.'))
);
if (!authoringReactViewEnabled) {
this.$scope.resolve(error);
}
return Promise.reject(error);
});
}
}
Expand Down
4 changes: 3 additions & 1 deletion client/extension_bridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@ import {EditorFieldVocabulary, IEditorFieldVocabularyProps} from './components/f

import {getVocabularyItemFieldTranslated} from './utils/vocabularies';
import {getUserInterfaceLanguageFromCV} from './utils/users';
import {isContentLinkToCoverageAllowed} from './utils/archive';

import {registerEditorField} from './components/fields/resources/registerEditorFields';
import {IAssignmentItem, IEditorFieldProps, IPlanningAppState, IPlanningItem} from 'interfaces';
import {isContentLinkToCoverageAllowed} from './utils/archive';

import PlanningDetailsWidget, {getItemPlanningInfo} from './components/PlanningDetailsWidget';

Expand Down Expand Up @@ -48,6 +48,7 @@ interface IExtensionBridge {
language?: string,
fallbackField?: string
): string;
isContentLinkToCoverageAllowed(item: IArticle): boolean;
};

components: {
Expand Down Expand Up @@ -91,6 +92,7 @@ export const extensionBridge: IExtensionBridge = {
utils: {
getUserInterfaceLanguageFromCV,
getVocabularyItemFieldTranslated,
isContentLinkToCoverageAllowed,
},
components: {
EditorFieldVocabulary,
Expand Down
151 changes: 95 additions & 56 deletions client/planning-extension/src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
ISuperdesk,
IExtensionActivationResult,
onPublishMiddlewareResult,
IAuthoringAction,
} from 'superdesk-api';
import {IPlanningAssignmentService} from './interfaces';
import {IPlanningConfig} from '../../interfaces';
Expand Down Expand Up @@ -113,69 +114,104 @@ const extension: IExtension = {
const displayTopbarWidget = superdesk.privileges.hasPrivilege('planning_assignments_view')
&& extensionConfig?.assignmentsTopBarWidget === true;
const {gettext} = superdesk.localization;
const {article: superdeskArticleApi} = superdesk.entities;
const planningActionsGroupId = 'planning-actions';
const {getItemPlanningInfo} = extensionBridge.planning;
const canAddToPlanning = (item: IArticle) => (
superdesk.privileges.hasPrivilege('planning_planning_management') &&
superdesk.privileges.hasPrivilege('archive') &&
!item.assignment_id != null &&
!superdesk.entities.article.isPersonal(item) &&
!superdesk.entities.article.isLockedInOtherSession(item) &&
item.state !== 'correction' &&
extensionBridge.ui.utils.isContentLinkToCoverageAllowed(item) &&
(
superdesk.entities.article.itemAction(item).edit ||
superdesk.entities.article.itemAction(item).correct ||
superdesk.entities.article.itemAction(item).deschedule
)
);
const canUnlinkAsCoverage = (item: IArticle) => (
superdesk.privileges.hasPrivilege('archive') &&
item.assignment_id != null &&
!superdeskArticleApi.isPersonal(item) &&
!superdeskArticleApi.isLockedInOtherSession(item) &&
(
superdeskArticleApi.itemAction(item).edit ||
superdeskArticleApi.itemAction(item).correct ||
superdeskArticleApi.itemAction(item).deschedule
)
);
const canFulfilAssignment = (item: IArticle) => {
const itemStates = ['killed', 'recalled', 'unpublished', 'spiked', 'correction'];
const {isContentLinkToCoverageAllowed} = extensionBridge.assignments.utils;

return (
!item.assignment_id &&
!superdesk.entities.article.isPersonal(item) &&
isContentLinkToCoverageAllowed(item) &&
!superdesk.entities.article.isLockedInOtherSession(item) &&
!itemStates.includes(item.state) &&
superdesk.privileges.hasPrivilege('archive')
);
};

const getPermittedActions = (item: IArticle) => {
const permittedActions: Array<IAuthoringAction> = [];

if (canAddToPlanning(item)) {
permittedActions.push({
label: gettext('Add to Planning'),
groupId: planningActionsGroupId,
icon: 'calendar-list',
onTrigger: () => {
const customEvent = new CustomEvent('planning:addToPlanning', {detail: item});

window.dispatchEvent(customEvent);
},
});
}

if (canUnlinkAsCoverage(item)) {
permittedActions.push({
label: gettext('Unlink as Coverage'),
groupId: planningActionsGroupId,
icon: 'cut',
onTrigger: () => {
superdesk.entities.article.get(item._id).then((_item) => {
window.dispatchEvent(new CustomEvent(
'planning:unlinkfromcoverage',
{detail: {item: _item}},
));
});
},
});
}

if (canFulfilAssignment(item)) {
permittedActions.push({
label: superdesk.localization.gettext('Fulfil assignment'),
groupId: planningActionsGroupId,
icon: 'calendar-list',
onTrigger: () => {
superdesk.entities.article.get(item._id).then((_item) => {
window.dispatchEvent(new CustomEvent(
'planning:fulfilassignment',
{detail: {item: _item}},
));
});
},
});
}

return permittedActions;
};

const result: IExtensionActivationResult = {
contributions: {
entities: {
article: {
getActions: (item) => [
{
label: gettext('Unlink as Coverage'),
groupId: planningActionsGroupId,
icon: 'cut',
onTrigger: () => {
const superdeskArticle = superdesk.entities.article;

if (
superdesk.privileges.hasPrivilege('archive') &&
item.assignment_id != null &&
!superdeskArticle.isPersonal(item) &&
!superdeskArticle.isLockedInOtherSession(item) &&
(
superdeskArticle.itemAction(item).edit ||
superdeskArticle.itemAction(item).correct ||
superdeskArticle.itemAction(item).deschedule
)
) {
superdeskArticle.get(item._id).then((_item) => {
window.dispatchEvent(new CustomEvent(
'planning:unlinkfromcoverage',
{detail: {item: _item}},
));
});
}
},
},
{
label: superdesk.localization.gettext('Fulfil assignment'),
groupId: planningActionsGroupId,
icon: 'calendar-list',
onTrigger: () => {
const itemStates = ['killed', 'recalled', 'unpublished', 'spiked', 'correction'];
const {isContentLinkToCoverageAllowed} = extensionBridge.assignments.utils;

if (
!item.assignment_id &&
!superdesk.entities.article.isPersonal(item) &&
isContentLinkToCoverageAllowed(item) &&
!superdesk.entities.article.isLockedInOtherSession(item) &&
!itemStates.includes(item.state) &&
superdesk.privileges.hasPrivilege('archive')
) {
superdesk.entities.article.get(item._id).then((_item) => {
window.dispatchEvent(new CustomEvent(
'planning:fulfilassignment',
{detail: {item: _item}},
));
});
} else {
superdesk.ui.notify.error('This action is not permitted');
}
},
}
],
getActions: (item) => getPermittedActions(item),
onSpike: (item: IArticle) => onSpike(superdesk, item),
onSpikeMultiple: (items: Array<IArticle>) => onSpikeMultiple(superdesk, items),
onPublish: (item: IArticle) => onPublishArticle(superdesk, item),
Expand All @@ -191,6 +227,9 @@ const extension: IExtension = {
},
},
},

// Current set of planning actions have to be displayed both in authoring and list item actions
getAuthoringActions: (item) => getPermittedActions(item),
notifications: {
'email:notification:assignments': {name: superdesk.localization.gettext('Assignment')}
},
Expand Down
4 changes: 2 additions & 2 deletions client/planning-extension/src/extension_bridge.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import * as React from 'react';
import {IVocabularyItem} from 'superdesk-api';
import {IArticle, IVocabularyItem} from 'superdesk-api';
import {IAssignmentItem, IEditorFieldProps, IPlanningAppState, IPlanningItem} from '../../interfaces';
import {IArticle} from 'superdesk-api';

interface IEditorFieldVocabularyProps extends IEditorFieldProps {
options: Array<any>;
Expand Down Expand Up @@ -44,6 +43,7 @@ interface IExtensionBridge {
language?: string,
fallbackField?: string
): string;
isContentLinkToCoverageAllowed(item: IArticle): boolean;
};
components: {
EditorFieldVocabulary: React.ComponentType<IEditorFieldVocabularyProps>;
Expand Down
Loading

0 comments on commit 47a308c

Please sign in to comment.