From 91febeb05a051247c6bcbf3a5109a8e85ec0a449 Mon Sep 17 00:00:00 2001 From: Remi Blom-Ohlsen Date: Thu, 18 Jan 2024 10:08:54 +0100 Subject: [PATCH] Move shared functions for Portfolio/Aggregating to shared PortalDataService [skip-install] --- .../ColumnFormPanel/useColumnFormPanel.ts | 12 +- .../ViewFormPanel/useViewFormPanel.ts | 10 +- .../useEditViewColumnsPanel.ts | 4 +- .../ColumnFormPanel/index.tsx | 1 + .../PortfolioWebParts/src/data/index.ts | 96 +------------- .../PortfolioWebParts/src/data/types.ts | 51 ------- .../ProgramWebParts/src/data/SPDataAdapter.ts | 104 +-------------- .../AllPropertiesPanel/index.tsx | 16 ++- .../TimelineList/useToolbarItems.tsx | 4 +- .../ProjectTimeline/data/fetchTimelineData.ts | 4 +- .../PortalDataService/PortalDataService.ts | 124 +++++++++++++++++- 11 files changed, 167 insertions(+), 259 deletions(-) diff --git a/SharePointFramework/PortfolioWebParts/src/components/PortfolioAggregation/ColumnFormPanel/useColumnFormPanel.ts b/SharePointFramework/PortfolioWebParts/src/components/PortfolioAggregation/ColumnFormPanel/useColumnFormPanel.ts index 1d2b21277..bdbdf3dad 100644 --- a/SharePointFramework/PortfolioWebParts/src/components/PortfolioAggregation/ColumnFormPanel/useColumnFormPanel.ts +++ b/SharePointFramework/PortfolioWebParts/src/components/PortfolioAggregation/ColumnFormPanel/useColumnFormPanel.ts @@ -36,8 +36,8 @@ export function useColumnFormPanel() { } try { if (isEditing) { - await context.props.dataAdapter - .updateProjectContentColumn(columnItem, persistRenderGlobally) + await context.props.dataAdapter.portalDataService + .updateProjectContentColumn('PROJECT_CONTENT_COLUMNS', columnItem, persistRenderGlobally) .then(() => { const editedColumn = new ProjectContentColumn(columnItem) context.dispatch(ADD_COLUMN(editedColumn)) @@ -50,8 +50,8 @@ export function useColumnFormPanel() { const updateItem: SPDataSourceItem = { GtProjectContentColumnsId: properties.Id } - context.props.dataAdapter - .updateDataSourceItem(updateItem, context.state.currentView?.title) + context.props.dataAdapter.portalDataService + .updateDataSourceItem('DATA_SOURCES', updateItem, context.state.currentView?.title) .then(() => { context.dispatch(ADD_COLUMN(newColumn)) }) @@ -61,8 +61,8 @@ export function useColumnFormPanel() { } const onDeleteColumn = async () => { - await context.props.dataAdapter - .deleteProjectContentColumn(context.state.columnForm.column) + await context.props.dataAdapter.portalDataService + .deleteProjectContentColumn('PROJECT_CONTENT_COLUMNS', context.state.columnForm.column) .then(() => { context.dispatch(DELETE_COLUMN()) }) diff --git a/SharePointFramework/PortfolioWebParts/src/components/PortfolioAggregation/ViewFormPanel/useViewFormPanel.ts b/SharePointFramework/PortfolioWebParts/src/components/PortfolioAggregation/ViewFormPanel/useViewFormPanel.ts index a03fde4dc..14cd07288 100644 --- a/SharePointFramework/PortfolioWebParts/src/components/PortfolioAggregation/ViewFormPanel/useViewFormPanel.ts +++ b/SharePointFramework/PortfolioWebParts/src/components/PortfolioAggregation/ViewFormPanel/useViewFormPanel.ts @@ -20,8 +20,14 @@ export function useViewFormPanel() { } /** - * Saves the changes made to the view by updating the item in the `DATA_SOURCES` list or adding a new item to the list. - * Dismisses the form panel by dispatching the `SET_VIEW_FORM_PANEL` action. + * Saves the column to the list. If the column is new, it will + * also add the column to the current view. If the column is + * being edited, it will update the column in the list. + * + * If the column is being edited, it will update the column in the list + * using `updateItemInList` from the `dataAdapter`. If the column is new, + * it will add the column to the list using `addItemToList` from + * the `dataAdapter`. */ const onSave = async () => { const { currentView } = context.state diff --git a/SharePointFramework/PortfolioWebParts/src/components/PortfolioAggregation/useEditViewColumnsPanel.ts b/SharePointFramework/PortfolioWebParts/src/components/PortfolioAggregation/useEditViewColumnsPanel.ts index 3976caeeb..999e924bf 100644 --- a/SharePointFramework/PortfolioWebParts/src/components/PortfolioAggregation/useEditViewColumnsPanel.ts +++ b/SharePointFramework/PortfolioWebParts/src/components/PortfolioAggregation/useEditViewColumnsPanel.ts @@ -23,8 +23,8 @@ export function useEditViewColumnsPanel( const properties: SPDataSourceItem = { GtProjectContentColumnsId: columns.map((c) => c.id) } - await context.props.dataAdapter - .updateDataSourceItem(properties, context.state.currentView?.title, true) + await context.props.dataAdapter.portalDataService + .updateDataSourceItem('DATA_SOURCES', properties, context.state.currentView?.title, true) .then(() => { context.dispatch(SET_COLUMNS({ columns })) onDismiss() diff --git a/SharePointFramework/PortfolioWebParts/src/components/PortfolioOverview/ColumnFormPanel/index.tsx b/SharePointFramework/PortfolioWebParts/src/components/PortfolioOverview/ColumnFormPanel/index.tsx index 08f665ec9..35e0325ba 100644 --- a/SharePointFramework/PortfolioWebParts/src/components/PortfolioOverview/ColumnFormPanel/index.tsx +++ b/SharePointFramework/PortfolioWebParts/src/components/PortfolioOverview/ColumnFormPanel/index.tsx @@ -39,6 +39,7 @@ export const ColumnFormPanel: FC = () => { findMatchingSearchProperty, columnMessages } = useColumnFormPanel() + return ( - col.GtDataSourceCategory === dataSourceCategory || - (!col.GtDataSourceCategory && !col.GtDataSourceLevel) || - (!col.GtDataSourceCategory && _.contains(col.GtDataSourceLevel, level)) - ) - return filteredColumnItems.map((item) => new ProjectContentColumn(item)) - } catch (error) { - throw new Error(format(strings.DataSourceCategoryError, dataSourceCategory)) - } - } - - public async updateProjectContentColumn( - columnItem: SPProjectContentColumnItem, - persistRenderAs = false - ): Promise { - try { - const list = this._sp.web.lists.getByTitle(strings.ProjectContentColumnsListName) - const properties: SPProjectContentColumnItem = _.pick( - columnItem, - [ - 'GtColMinWidth', - 'GtColMaxWidth', - persistRenderAs && 'GtFieldDataTypeProperties', - persistRenderAs && 'GtFieldDataType' - ].filter(Boolean) - ) - return await list.items.getById(columnItem.Id).update(properties) - } catch (error) { - throw new Error(error) - } - } - - public async deleteProjectContentColumn(column: Record): Promise { - try { - const list = this._sp.web.lists.getByTitle(strings.ProjectContentColumnsListName) - const items = await list.items() - const item = items.find((i) => i.GtManagedProperty === column.fieldName) - - if (!item) { - throw new Error(format(strings.ProjectContentColumnItemNotFound, column.fieldName)) - } - - const itemDeleteResult = list.items.getById(item.Id).delete() - return itemDeleteResult - } catch (error) { - throw new Error(error) - } - } - public async deleteItemFromList(listName: string, itemId: number): Promise { try { const list = this._sp.web.lists.getByTitle(listName) @@ -847,32 +789,6 @@ export class DataAdapter implements IPortfolioWebPartsDataAdapter { return false } } - - public async updateDataSourceItem( - properties: SPDataSourceItem, - dataSourceTitle: string, - shouldReplace: boolean = false - ): Promise { - try { - const list = this._sp.web.lists.getByTitle(strings.DataSourceListName) - const [item] = await list.items.filter(`Title eq '${dataSourceTitle}'`)() - if (!item) { - throw new Error(format(strings.DataSourceItemNotFound, dataSourceTitle)) - } - if (item.GtProjectContentColumnsId && !shouldReplace) { - properties.GtProjectContentColumnsId = [ - ...item.GtProjectContentColumnsId, - properties.GtProjectContentColumnsId - ] - return await list.items.getById(item.Id).update(properties) - } else { - properties.GtProjectContentColumnsId = properties.GtProjectContentColumnsId as number[] - return await list.items.getById(item.Id).update(properties) - } - } catch (error) { - throw new Error(error) - } - } } export * from './types' diff --git a/SharePointFramework/PortfolioWebParts/src/data/types.ts b/SharePointFramework/PortfolioWebParts/src/data/types.ts index d34519609..c945fcd36 100644 --- a/SharePointFramework/PortfolioWebParts/src/data/types.ts +++ b/SharePointFramework/PortfolioWebParts/src/data/types.ts @@ -1,5 +1,4 @@ import { WebPartContext } from '@microsoft/sp-webpart-base' -import { IItemUpdateResult } from '@pnp/sp/items' import { ISiteUserInfo } from '@pnp/sp/presets/all' import { ISearchResult, SortDirection } from '@pnp/sp/search' import { @@ -10,9 +9,7 @@ import { PortfolioOverviewView, ProjectContentColumn, ProjectListModel, - SPDataSourceItem, SPProjectColumnItem, - SPProjectContentColumnItem, SPProjectItem, TimelineConfigurationModel, TimelineContentModel @@ -281,41 +278,6 @@ export interface IPortfolioWebPartsDataAdapter { dataSourceCategory?: string ): Promise - /** - * Fetch project content columns from the project content columns SharePoint list on the hub site - * with the specified `dataSourceCategory` or without a category. The result is transformed into - * `ProjectColumn` objects. The `renderAs` property is set to the `dataType` property in lower case - * and with spaces replaced with underscores. - * - * If the `dataSourceCategory` is null or empty, an empty array is returned. - * - * @param category Category for data source - * @param level Level for data source - */ - fetchProjectContentColumns?( - dataSourceCategory: string, - level?: string - ): Promise - - /** - * Update project content column with new values for properties `GtColMinWidth` and `GtColMaxWidth`, - * as well as the `GtFieldDataType` property if parameter `persistRenderAs` is true. - * - * @param column Project content column - * @param persistRenderAs Persist render as property - */ - updateProjectContentColumn?( - columnItem: SPProjectContentColumnItem, - persistRenderAs?: boolean - ): Promise - - /** - * Delete project content column - * - * @param column Column to delete - */ - deleteProjectContentColumn?(property: Record): Promise - /** * Adds a new column to the project columns list and adds the column to the specified view. * @@ -326,17 +288,4 @@ export interface IPortfolioWebPartsDataAdapter { properties: SPProjectColumnItem, view: PortfolioOverviewView ): Promise - - /** - * Update the data source item with title `dataSourceTitle` with the properties in `properties`. - * - * @param properties Properties - * @param dataSourceTitle Data source title - * @param shouldReplace Should replace the existing columns - */ - updateDataSourceItem?( - properties: SPDataSourceItem, - dataSourceTitle: string, - shouldReplace?: boolean - ): Promise } diff --git a/SharePointFramework/ProgramWebParts/src/data/SPDataAdapter.ts b/SharePointFramework/ProgramWebParts/src/data/SPDataAdapter.ts index f98d2bbff..5a7b20a57 100644 --- a/SharePointFramework/ProgramWebParts/src/data/SPDataAdapter.ts +++ b/SharePointFramework/ProgramWebParts/src/data/SPDataAdapter.ts @@ -1,7 +1,7 @@ import { format } from '@fluentui/react/lib/Utilities' import { flatten } from '@microsoft/sp-lodash-subset' import { WebPartContext } from '@microsoft/sp-webpart-base' -import { PnPClientStorage, dateAdd, stringIsNullOrEmpty } from '@pnp/core' +import { PnPClientStorage, dateAdd } from '@pnp/core' import '@pnp/sp/items/get-all' import { ISearchResult, @@ -34,8 +34,6 @@ import { ProjectDataService, ProjectListModel, SPDataAdapterBase, - SPDataSourceItem, - SPProjectContentColumnItem, SPProjectItem, TimelineConfigurationModel, TimelineContentModel, @@ -45,7 +43,6 @@ import _ from 'underscore' import { DEFAULT_SEARCH_SETTINGS, IProjectsData } from './types' import { IList } from '@pnp/sp/lists' import { IItem } from '@pnp/sp/items' -import { IItemUpdateResult } from '@pnp/sp/presets/all' /** * `SPDataAdapter` is a class that extends the `SPDataAdapterBase` class and implements the `IPortfolioWebPartsDataAdapter` interface. @@ -115,7 +112,11 @@ export class SPDataAdapter level: string = 'Overordnet/Program' ): Promise { try { - const columns = await this.fetchProjectContentColumns(category, level) + const columns = await this.portalDataService.fetchProjectContentColumns( + 'PROJECT_CONTENT_COLUMNS', + category, + level + ) const [views, viewsUrls, columnUrls] = await Promise.all([ this.fetchDataSources(category, level, columns), this.portalDataService.getListFormUrls('DATA_SOURCES'), @@ -764,31 +765,6 @@ export class SPDataAdapter } } - public async fetchProjectContentColumns(dataSourceCategory: string, level?: string) { - try { - if (stringIsNullOrEmpty(dataSourceCategory)) return [] - const projectContentColumnsList = this.portalDataService.web.lists.getByTitle( - strings.ProjectContentColumnsListName - ) - const columnItems = await projectContentColumnsList.items - .select(...Object.keys(new SPProjectContentColumnItem())) - .using(DefaultCaching)() - const filteredColumnItems = columnItems.filter( - (col) => - col.GtDataSourceCategory === dataSourceCategory || - (!col.GtDataSourceCategory && !col.GtDataSourceLevel) || - (!col.GtDataSourceCategory && _.contains(col.GtDataSourceLevel, level)) - ) - return filteredColumnItems.map((item) => { - const col = new ProjectContentColumn(item) - const renderAs = (col.dataType ? col.dataType.toLowerCase() : 'text').split(' ').join('_') - return col.setData({ renderAs }) - }) - } catch (error) { - throw new Error(format(strings.DataSourceCategoryError, dataSourceCategory)) - } - } - /** * Update project item/entity in hub site (portfolio) * @@ -967,72 +943,4 @@ export class SPDataAdapter await this.updateProjectInHub(updateProperties) return updatedProjects } - - public async updateProjectContentColumn( - columnItem: SPProjectContentColumnItem, - persistRenderAs = false - ): Promise { - try { - const list = this.portalDataService.web.lists.getByTitle( - strings.ProjectContentColumnsListName - ) - const properties: SPProjectContentColumnItem = _.pick( - columnItem, - [ - 'GtColMinWidth', - 'GtColMaxWidth', - persistRenderAs && 'GtFieldDataTypeProperties', - persistRenderAs && 'GtFieldDataType' - ].filter(Boolean) - ) - return await list.items.getById(columnItem.Id).update(properties) - } catch (error) { - throw new Error(error) - } - } - - public async deleteProjectContentColumn(column: Record): Promise { - try { - const list = this.portalDataService.web.lists.getByTitle( - strings.ProjectContentColumnsListName - ) - const items = await list.items() - const item = items.find((i) => i.GtManagedProperty === column.fieldName) - - if (!item) { - throw new Error(format(strings.ProjectContentColumnItemNotFound, column.fieldName)) - } - - const itemDeleteResult = list.items.getById(item.Id).delete() - return itemDeleteResult - } catch (error) { - throw new Error(error) - } - } - - public async updateDataSourceItem( - properties: SPDataSourceItem, - dataSourceTitle: string, - shouldReplace: boolean = false - ): Promise { - try { - const list = this.portalDataService.web.lists.getByTitle(strings.DataSourceListName) - const [item] = await list.items.filter(`Title eq '${dataSourceTitle}'`)() - if (!item) { - throw new Error(format(strings.DataSourceItemNotFound, dataSourceTitle)) - } - if (item.GtProjectContentColumnsId && !shouldReplace) { - properties.GtProjectContentColumnsId = [ - ...item.GtProjectContentColumnsId, - properties.GtProjectContentColumnsId - ] - return await list.items.getById(item.Id).update(properties) - } else { - properties.GtProjectContentColumnsId = properties.GtProjectContentColumnsId as number[] - return await list.items.getById(item.Id).update(properties) - } - } catch (error) { - throw new Error(error) - } - } } diff --git a/SharePointFramework/ProjectWebParts/src/components/ProjectInformation/AllPropertiesPanel/index.tsx b/SharePointFramework/ProjectWebParts/src/components/ProjectInformation/AllPropertiesPanel/index.tsx index 65f1ab978..b6db61867 100644 --- a/SharePointFramework/ProjectWebParts/src/components/ProjectInformation/AllPropertiesPanel/index.tsx +++ b/SharePointFramework/ProjectWebParts/src/components/ProjectInformation/AllPropertiesPanel/index.tsx @@ -8,15 +8,17 @@ import { useProjectInformationContext } from '../context' export const AllPropertiesPanel: FC = (props) => { const context = useProjectInformationContext() - return context.dispatch(CLOSE_PANEL())} - onRenderBody={() => } - /> + return ( + context.dispatch(CLOSE_PANEL())} + onRenderBody={() => } + /> + ) } AllPropertiesPanel.defaultProps = { $type: 'AllPropertiesPanel', - headerText: strings.ProjectPropertiesHeader, + headerText: strings.ProjectPropertiesHeader } diff --git a/SharePointFramework/ProjectWebParts/src/components/ProjectTimeline/TimelineList/useToolbarItems.tsx b/SharePointFramework/ProjectWebParts/src/components/ProjectTimeline/TimelineList/useToolbarItems.tsx index d6d5beb35..2f2ae0b99 100644 --- a/SharePointFramework/ProjectWebParts/src/components/ProjectTimeline/TimelineList/useToolbarItems.tsx +++ b/SharePointFramework/ProjectWebParts/src/components/ProjectTimeline/TimelineList/useToolbarItems.tsx @@ -19,7 +19,9 @@ export function useToolbarItems() { * @param item Item */ const deleteTimelineItem = async () => { - const list = SPDataAdapter.portalDataService.web.lists.getByTitle(strings.TimelineContentListName) + const list = SPDataAdapter.portalDataService.web.lists.getByTitle( + strings.TimelineContentListName + ) const selectedItems = context.state.selectedItems.map((id) => context.state.data.listItems.find((_, idx) => idx === id) diff --git a/SharePointFramework/ProjectWebParts/src/components/ProjectTimeline/data/fetchTimelineData.ts b/SharePointFramework/ProjectWebParts/src/components/ProjectTimeline/data/fetchTimelineData.ts index b4959abef..7e4ff8cf8 100644 --- a/SharePointFramework/ProjectWebParts/src/components/ProjectTimeline/data/fetchTimelineData.ts +++ b/SharePointFramework/ProjectWebParts/src/components/ProjectTimeline/data/fetchTimelineData.ts @@ -52,7 +52,9 @@ export async function fetchTimelineData( const defaultViewColumns = ( await timelineContentList.defaultView.fields.select('Items').top(500)() )['Items'] as string[] - const timelineContentFields = await SPDataAdapter.portalDataService.getListFields('TIMELINE_CONTENT') + const timelineContentFields = await SPDataAdapter.portalDataService.getListFields( + 'TIMELINE_CONTENT' + ) const timelineContentEditableFields = timelineContentFields.map( (fld) => new EditableSPField(fld) ) diff --git a/SharePointFramework/shared-library/src/services/PortalDataService/PortalDataService.ts b/SharePointFramework/shared-library/src/services/PortalDataService/PortalDataService.ts index 3721f2082..445828823 100644 --- a/SharePointFramework/shared-library/src/services/PortalDataService/PortalDataService.ts +++ b/SharePointFramework/shared-library/src/services/PortalDataService/PortalDataService.ts @@ -3,7 +3,7 @@ import { AssignFrom, dateAdd, PnPClientStorage, stringIsNullOrEmpty } from '@pnp import { Logger, LogLevel } from '@pnp/logging' import { IFolder } from '@pnp/sp/folders' import { ICamlQuery, IList } from '@pnp/sp/lists' -import { IItemUpdateResultData, spfi, SPFI } from '@pnp/sp/presets/all' +import { IItemUpdateResult, IItemUpdateResultData, spfi, SPFI } from '@pnp/sp/presets/all' import { PermissionKind } from '@pnp/sp/security' import { IWeb } from '@pnp/sp/webs' import initJsom, { ExecuteJsomQuery as executeQuery } from 'spfx-jsom' @@ -16,13 +16,16 @@ import { ProjectAdminRole, ProjectColumn, ProjectColumnConfig, + ProjectContentColumn, ProjectTemplate, SectionModel, + SPDataSourceItem, SPField, SPPortfolioOverviewViewItem, SPProjectAdminRoleItem, SPProjectColumnConfigItem, SPProjectColumnItem, + SPProjectContentColumnItem, StatusReport, StatusReportAttachment } from '../../models' @@ -38,6 +41,7 @@ import { } from './types' import { DataService } from '../DataService' import '@pnp/sp/presets/all' +import _ from 'underscore' export class PortalDataService extends DataService { private _sp: SPFI @@ -541,6 +545,124 @@ export class PortalDataService extends DataService { + try { + const list = this._getList(_list) + const properties: SPProjectContentColumnItem = _.pick( + columnItem, + [ + 'GtColMinWidth', + 'GtColMaxWidth', + persistRenderAs && 'GtFieldDataTypeProperties', + persistRenderAs && 'GtFieldDataType' + ].filter(Boolean) + ) + return await list.items.getById(columnItem.Id).update(properties) + } catch (error) { + throw new Error(error) + } + } + + /** + * Delete project content column + * + * @param _list List + * @param column Column to delete + */ + public async deleteProjectContentColumn( + _list: PortalDataServiceList, + column: Record + ): Promise { + try { + const list = this._getList(_list) + const items = await list.items() + const item = items.find((i) => i.GtManagedProperty === column.fieldName) + const itemDeleteResult = list.items.getById(item.Id).delete() + return itemDeleteResult + } catch (error) { + throw new Error(error) + } + } + + /** + * Update the data source item with title `dataSourceTitle` with the properties in `properties`. + * + * @param _list List + * @param properties Properties + * @param dataSourceTitle Data source title + * @param shouldReplace Should replace the existing columns + */ + public async updateDataSourceItem( + _list: PortalDataServiceList, + properties: SPDataSourceItem, + dataSourceTitle: string, + shouldReplace: boolean = false + ): Promise { + try { + const list = this._getList(_list) + const [item] = await list.items.filter(`Title eq '${dataSourceTitle}'`)() + if (item.GtProjectContentColumnsId && !shouldReplace) { + properties.GtProjectContentColumnsId = [ + ...item.GtProjectContentColumnsId, + properties.GtProjectContentColumnsId + ] + return await list.items.getById(item.Id).update(properties) + } else { + properties.GtProjectContentColumnsId = properties.GtProjectContentColumnsId as number[] + return await list.items.getById(item.Id).update(properties) + } + } catch (error) { + throw new Error(error) + } + } + + /** + * Fetch project content columns from the project content columns SharePoint list on the hub site + * with the specified `dataSourceCategory` or without a category. The result is transformed into + * `ProjectColumn` objects. The `renderAs` property is set to the `dataType` property in lower case + * and with spaces replaced with underscores. + * + * If the `dataSourceCategory` is null or empty, an empty array is returned. + * + * @param _list List + * @param category Category for data source + * @param level Level for data source + */ + public async fetchProjectContentColumns( + _list: PortalDataServiceList, + dataSourceCategory: string, + level?: string + ) { + try { + if (stringIsNullOrEmpty(dataSourceCategory)) return [] + const list = this._getList(_list) + const columnItems = await list.items.select( + ...Object.keys(new SPProjectContentColumnItem()) + )() + const filteredColumnItems = columnItems.filter( + (col) => + col.GtDataSourceCategory === dataSourceCategory || + (!col.GtDataSourceCategory && !col.GtDataSourceLevel) || + (!col.GtDataSourceCategory && _.contains(col.GtDataSourceLevel, level)) + ) + return filteredColumnItems.map((item) => new ProjectContentColumn(item)) + } catch (error) { + throw new Error(error) + } + } + /** * Add item to a list *