Integrating r1936 - ccm-cms: Added new Reports tab to the content center, initially with 'Content Section Summar' report.

git-svn-id: https://svn.libreccm.org/ccm/trunk@248 8810af33-2d31-482b-a856-94f89814c4df
master
pb 2009-08-28 16:32:01 +00:00
parent dcce15da7c
commit 1815a1f7f6
8 changed files with 686 additions and 0 deletions

View File

@ -0,0 +1,68 @@
//
// Copyright (C) 2009 Permeance Technologies Pty Ltd. All Rights Reserved.
//
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public License
// as published by the Free Software Foundation; either version 2.1 of
// the License, or (at your option) any later version.
//
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
//
model com.arsdigita.cms;
query getContentSectionSummary {
BigDecimal folderId;
String folder;
BigDecimal subfolderCount;
do {
SELECT ci.item_id as folder_id,
ci.name AS folder,
(SELECT COUNT(*)-1
FROM cms_items ci2
WHERE ci2.type_id is null
START WITH ci2.item_id = ci.item_id
CONNECT BY PRIOR ci2.item_id = ci2.parent_id
) AS subfolder_count
FROM content_sections cs,
cms_items ci
WHERE ci.section_id = cs.section_id
AND cs.section_id = :sectionId
AND ci.type_id is null
AND ci.master_id is null
AND ((ci.parent_id = cs.root_folder_id) or (ci.item_id = cs.root_folder_id))
order by ci.name
} map {
folderId = folder_id;
folder = folder;
subfolderCount = subfolder_count;
}
}
query getContentTypeCountPerFolder {
String contentType;
Long typeCount;
do {
SELECT ct.label as content_type,
COUNT(*) as type_count
FROM content_types ct,
(SELECT *
FROM cms_items ci
START WITH ci.parent_id = :folderId
CONNECT BY PRIOR ci.item_id = ci.parent_id and ci.language is null) ci
WHERE ci.type_id = ct.type_id
GROUP BY ct.label
} map {
contentType = content_type;
typeCount = type_count;
}
}

View File

@ -823,3 +823,18 @@ Thank you for using {3}.\n
cms.contentassets.ui.description=Description
cms.ui.content_check_alert.subject=Content Check Alert
cms.ui.content_check_alert.body=Dear Author\n\nYou are listed as the Content Owner for the following {0} web page(s). Please check if they need amending or updating in any way:\n\n{1}\nIf you have any amendments or questions please email the appropriate web editor:\n\nsally.editor@aplaws.org (Council & Democracy, Environment, Education)\njohn.editor@aplaws.org (Community & Living, Business, Social Care)\nfrank.editor@aplaws.org (Council & Democracy, Environment, Education)\n\nYou can contact webmanager@aplaws.org with any questions about this email\n
#
# Reports section
#
cms.ui.reports=Reports
cms.ui.reports.header=Reports
cms.ui.reports.intro=Please select a report.
# Columns for ContentSectionSummary report
cms.ui.reports.css.emptyResult=No matching content items found.
cms.ui.reports.css.reportName=Content Section Summary
cms.ui.reports.css.contentSection=Content Section
cms.ui.reports.css.folder=Folder
cms.ui.reports.css.subfolderCount=Subfolders
cms.ui.reports.css.contentType=Content Type
cms.ui.reports.css.draft=Draft
cms.ui.reports.css.live=Live

View File

@ -142,6 +142,7 @@ public class ContentSectionPage extends CMSPage implements ActionListener {
private ContentTypeAdminPane m_typePane;
private UserAdminPane m_userAdminPane;
private ContentSoonExpiredPane m_csePane;
private ReportPane m_reportPane;
private static class TitlePrinter implements PrintListener {
public void prepare(PrintEvent e) {
@ -173,6 +174,7 @@ public class ContentSectionPage extends CMSPage implements ActionListener {
m_typePane = getContentTypeAdminPane();
m_userAdminPane = getUserAdminPane();
m_csePane = getCSEPane();
m_reportPane = getReportPane();
// The panes
m_tabbedPane = createTabbedPane();
@ -204,6 +206,7 @@ public class ContentSectionPage extends CMSPage implements ActionListener {
m_tabbedPane.setTabVisible(state, m_rolePane, sm.canAccess(user,SecurityConstants.STAFF_ADMIN));
// csePane: should check permission
m_tabbedPane.setTabVisible(state, m_csePane, true);
// TODO Check for reportPane as well
}
}
});
@ -313,6 +316,13 @@ public class ContentSectionPage extends CMSPage implements ActionListener {
return m_csePane;
}
protected ReportPane getReportPane() {
if (m_reportPane == null) {
m_reportPane = new ReportPane();
}
return m_reportPane;
}
/**
* Adds the specified component, with the specified tab name, to
* the tabbed pane only if it is not null.
@ -359,6 +369,7 @@ public class ContentSectionPage extends CMSPage implements ActionListener {
tab(pane, "cms.ui.content_types", getContentTypeAdminPane());
tab(pane, "cms.ui.user_admin", getUserAdminPane());
tab(pane, "cms.ui.cse", getCSEPane());
tab(pane, "cms.ui.reports", getReportPane());
return pane;
}

View File

@ -0,0 +1,158 @@
/*
* Copyright (C) 2009 Permeance Technologies Pty Ltd. All Rights Reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package com.arsdigita.cms.ui;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import com.arsdigita.bebop.Label;
import com.arsdigita.bebop.List;
import com.arsdigita.bebop.PageState;
import com.arsdigita.bebop.ParameterSingleSelectionModel;
import com.arsdigita.bebop.SingleSelectionModel;
import com.arsdigita.bebop.event.ChangeEvent;
import com.arsdigita.bebop.event.ChangeListener;
import com.arsdigita.bebop.list.ListModel;
import com.arsdigita.bebop.list.ListModelBuilder;
import com.arsdigita.bebop.parameters.StringParameter;
import com.arsdigita.cms.ui.report.ContentSectionSummaryTable;
import com.arsdigita.cms.ui.report.Report;
import com.arsdigita.cms.ui.report.ReportListModel;
import com.arsdigita.toolbox.ui.ActionGroup;
import com.arsdigita.toolbox.ui.Section;
import com.arsdigita.util.LockableImpl;
/**
* A pane that shows selectable reports and their results.
* A selectable list of reports is shown on the left-hand side, a selected report is shown as
* body.
*
* @author <a href="https://sourceforge.net/users/thomas-buckel/">thomas-buckel</a>
* @author <a href="https://sourceforge.net/users/tim-permeance/">tim-permeance</a>
*/
public class ReportPane extends BaseAdminPane {
private final SingleSelectionModel m_selectionModel;
private final java.util.List<Report> m_availableReports;
public ReportPane() {
m_availableReports = getReports();
m_selectionModel = new ParameterSingleSelectionModel(new StringParameter(List.SELECTED));
m_selectionModel.addChangeListener(new SelectionListener());
setSelectionModel(m_selectionModel);
List m_reports = new List(new ReportListModelBuilder(m_availableReports));
m_reports.setSelectionModel(m_selectionModel);
final ReportsListSection reportsListSection = new ReportsListSection(m_reports);
setLeft(reportsListSection);
// Register the actual components of the reports for later usage
for (Report report : m_availableReports) {
getBody().add(report.getComponent());
}
setIntroPane(new Label(gz("cms.ui.reports.intro")));
}
/**
* @return List of available reports.
*/
private java.util.List<Report> getReports() {
java.util.List<Report> reports = new ArrayList<Report>();
reports.add(new Report("cms.ui.reports.css.reportName", new ContentSectionSummaryTable()));
// Add other reports as required
Collections.sort(reports, new Comparator<Report>() {
@Override
public int compare(Report r1, Report r2) {
return r1.getName().compareTo(r2.getName());
}
});
return reports;
}
/**
* Get the report model that matches the given key.
* @param key Key to match.
* @return Report model that matches that given key, null if no matching report was found.
*/
private Report getReportByKey(String key) {
for (Report report : m_availableReports) {
if (report.getKey().equals(key)) {
return report;
}
}
return null;
}
/**
* UI section for left-hand list of reports.
*/
private class ReportsListSection extends Section {
ReportsListSection(List reports) {
setHeading(gz("cms.ui.reports.header"));
ActionGroup group = new ActionGroup();
setBody(group);
group.setSubject(reports);
}
}
/**
* SelectionListener for selected report. It shows the selected report in the body of this
* component.
*/
private class SelectionListener implements ChangeListener {
public final void stateChanged(final ChangeEvent e) {
final PageState state = e.getPageState();
getBody().reset(state);
if (m_selectionModel.isSelected(state)) {
Report selectedReport = getReportByKey(m_selectionModel.getSelectedKey(state).toString());
if (selectedReport != null) {
getBody().push(state, selectedReport.getComponent());
}
}
}
}
/**
* ListModelBuilder creating a ReportListModel for a list of reports.
*/
private static class ReportListModelBuilder extends LockableImpl implements ListModelBuilder {
private java.util.List<Report> reports;
private ReportListModelBuilder(java.util.List<Report> reports) {
this.reports = reports;
}
public final ListModel makeModel(final List list,
final PageState state) {
return new ReportListModel(reports);
}
}
}

View File

@ -0,0 +1,263 @@
/*
* Copyright (C) 2009 Permeance Technologies Pty Ltd. All Rights Reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package com.arsdigita.cms.ui.report;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import com.arsdigita.bebop.PageState;
import com.arsdigita.bebop.Table;
import com.arsdigita.bebop.table.AbstractTableModelBuilder;
import com.arsdigita.bebop.table.TableModel;
import com.arsdigita.cms.CMS;
import com.arsdigita.cms.Folder;
import com.arsdigita.persistence.DataCollection;
import com.arsdigita.persistence.DataQuery;
import com.arsdigita.persistence.DataQueryDataCollectionAdapter;
import com.arsdigita.persistence.Session;
import com.arsdigita.persistence.SessionManager;
/**
* TableModelBuilder that creates a model for the content section summary report.
*
* @author <a href="https://sourceforge.net/users/thomas-buckel/">thomas-buckel</a>
*/
public class ContentSectionSummaryReportTableModelBuilder extends AbstractTableModelBuilder
{
@Override
public TableModel makeModel(Table t, PageState s)
{
Session session = SessionManager.getSession();
DataQuery query = session.retrieveQuery("com.arsdigita.cms.getContentSectionSummary");
query.setParameter("sectionId", CMS.getContext().getContentSection().getID());
return new CSSRModel(new DataQueryDataCollectionAdapter(query, "folder"));
}
/**
* Generates a table model by combining a list of top level folder (content items) with statistics about
* content items with in the folder in draft and live versions.
*/
private class CSSRModel implements TableModel
{
private final DataCollection m_folders;
private String m_folderName;
private BigDecimal m_subfolderCount;
private ContentTypeStatistics m_currentStatsRow;
private Iterator<ContentTypeStatistics> m_contentTypeStatIter;
CSSRModel(DataCollection folders)
{
m_folders = folders;
}
@Override
public final int getColumnCount()
{
return 5;
}
/**
* Combines an 'outer' iterator over the given DataCollection with an 'inner' iterator
* that contains rows for each row of the outer collection.
* {@inheritDoc}
*/
@Override
public final boolean nextRow()
{
if ((m_contentTypeStatIter == null) && m_folders.next())
{
m_folderName = (String) m_folders.get("folder");
m_subfolderCount = (BigDecimal) m_folders.get("subfolderCount");
Folder draftFolder = new Folder((BigDecimal) m_folders.get("folderId"));
m_contentTypeStatIter = retrieveContentTypeStats(draftFolder);
if (m_contentTypeStatIter.hasNext())
{
m_currentStatsRow = m_contentTypeStatIter.next();
}
else
{
// Rather than recursing into nextRow() again, a m_currentStatsRow == null
// is rendered to show one row for the folder but with no content type or values.
m_contentTypeStatIter = null;
m_currentStatsRow = null;
}
return true;
}
else if (m_contentTypeStatIter != null)
{
if (m_contentTypeStatIter.hasNext())
{
m_currentStatsRow = m_contentTypeStatIter.next();
}
else
{
m_contentTypeStatIter = null;
return nextRow();
}
return true;
}
else
{
m_folders.close();
return false;
}
}
@Override
public final Object getKeyAt(final int column)
{
return -1;
}
@Override
public final Object getElementAt(final int column)
{
switch (column)
{
case 0:
return m_folderName;
case 1:
return m_subfolderCount.toString();
case 2:
return (m_currentStatsRow != null) ? m_currentStatsRow.getContentType() : "N/A";
case 3:
return (m_currentStatsRow != null) ? m_currentStatsRow.getDraftCount() : "N/A";
case 4:
return (m_currentStatsRow != null) ? m_currentStatsRow.getLiveCount() : "N/A";
default:
throw new IllegalArgumentException("Illegal column index " + column);
}
}
/**
* Retrieve a list of content types used within a folder and for each content type
* the number of draft and live content items of this type.
*
* @param draftFolder Draft folder to retrieve stats for.
*
* @return Iterator over the retrieved statistics. Empty iterator with no results
* where found.
*/
private Iterator<ContentTypeStatistics> retrieveContentTypeStats(Folder draftFolder)
{
Session session = SessionManager.getSession();
// Query the number of content items per content type for drafts
DataQuery query = session.retrieveQuery("com.arsdigita.cms.getContentTypeCountPerFolder");
query.setParameter("folderId", draftFolder.getID());
DataCollection types = new DataQueryDataCollectionAdapter(query, "types");
Map<String, Long> draftContentTypeCounts = new HashMap<String, Long>();
try
{
while (types.next())
{
draftContentTypeCounts.put((String) types.get("contentType"), (Long) types.get("typeCount"));
}
}
finally
{
types.close();
}
// If there's a live version of the folder, query the number of content items per content type for it
// and merge both draft and live numbers
List<ContentTypeStatistics> result = new ArrayList<ContentTypeStatistics>();
Folder liveFolder = (Folder) draftFolder.getLiveVersion();
if (liveFolder != null)
{
query = session.retrieveQuery("com.arsdigita.cms.getContentTypeCountPerFolder");
query.setParameter("folderId", liveFolder.getID());
types = new DataQueryDataCollectionAdapter(query, "types");
try
{
while (types.next())
{
String contentType = (String) types.get("contentType");
long draftCount = (draftContentTypeCounts.get(contentType) != null) ? draftContentTypeCounts
.get(contentType) : 0;
long liveCount = (Long) types.get("typeCount");
result.add(new ContentTypeStatistics(contentType, draftCount, liveCount));
draftContentTypeCounts.remove(contentType);
}
}
finally
{
types.close();
}
}
// Add all draft stats that haven't been merged
for (Map.Entry<String, Long> draftCount : draftContentTypeCounts.entrySet())
{
result.add(new ContentTypeStatistics(draftCount.getKey(), draftCount.getValue(), 0));
}
return result.iterator();
}
}
/**
* Value object that holds content type statistics for a folder.
*/
private static class ContentTypeStatistics
{
private final String m_contentType;
private final long m_draftCount;
private final long m_liveCount;
public ContentTypeStatistics(String contentType, long draftCount, long liveCount)
{
m_contentType = contentType;
m_draftCount = draftCount;
m_liveCount = liveCount;
}
public String getContentType()
{
return m_contentType;
}
public long getDraftCount()
{
return m_draftCount;
}
public long getLiveCount()
{
return m_liveCount;
}
}
}

View File

@ -0,0 +1,48 @@
/*
* Copyright (C) 2009 Permeance Technologies Pty Ltd. All Rights Reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package com.arsdigita.cms.ui.report;
import com.arsdigita.bebop.Label;
import com.arsdigita.bebop.Table;
import com.arsdigita.cms.util.GlobalizationUtil;
/**
* Table component for content section summary report.
*
* @author <a href="https://sourceforge.net/users/thomas-buckel/">thomas-buckel</a>
*/
public class ContentSectionSummaryTable extends Table
{
private static final String[] s_fixedReportColumns = new String[] { lz("cms.ui.reports.css.folder"),
lz("cms.ui.reports.css.subfolderCount"), lz("cms.ui.reports.css.contentType"),
lz("cms.ui.reports.css.draft"), lz("cms.ui.reports.css.live"), };
public ContentSectionSummaryTable()
{
super(new ContentSectionSummaryReportTableModelBuilder(), s_fixedReportColumns);
setEmptyView(new Label(lz("cms.ui.reports.css.emptyResult")));
}
private static String lz(final String key)
{
return (String) GlobalizationUtil.globalize(key).localize();
}
}

View File

@ -0,0 +1,65 @@
/*
* Copyright (C) 2009 Permeance Technologies Pty Ltd. All Rights Reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package com.arsdigita.cms.ui.report;
import com.arsdigita.bebop.Component;
import com.arsdigita.cms.util.GlobalizationUtil;
import com.arsdigita.globalization.GlobalizedMessage;
import com.arsdigita.util.Assert;
/**
* UI model for a report.
* A report has a name and a component that displays the report.
*
* @author <a href="https://sourceforge.net/users/thomas-buckel/">thomas-buckel</a>
* @author <a href="https://sourceforge.net/users/tim-permeance/">tim-permeance</a>
*/
public class Report {
private final String m_key;
private final String m_name;
private final Component m_component;
public Report(String key, Component component) {
Assert.exists(key, "Key for report is required");
Assert.isTrue(key.length() > 0, "Key for report must not be empty");
Assert.exists(component, "Component for report is required");
m_key = key;
m_name = gz(m_key).localize().toString();
m_component = component;
}
public String getKey() {
return m_key;
}
public String getName() {
return m_name;
}
public Component getComponent() {
return m_component;
}
protected final static GlobalizedMessage gz(final String key) {
return GlobalizationUtil.globalize(key);
}
}

View File

@ -0,0 +1,58 @@
/*
* Copyright (C) 2009 Permeance Technologies Pty Ltd. All Rights Reserved.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public License
* as published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package com.arsdigita.cms.ui.report;
import java.util.List;
import com.arsdigita.bebop.list.ListModel;
import com.arsdigita.util.Assert;
/**
* ListModel for Reports.
*
* @author <a href="https://sourceforge.net/users/thomas-buckel/">thomas-buckel</a>
* @author <a href="https://sourceforge.net/users/tim-permeance/">tim-permeance</a>
*/
public class ReportListModel implements ListModel {
private int m_index = -1;
private final List<Report> m_reports;
public ReportListModel(List<Report> reports) {
Assert.exists(reports);
m_reports = reports;
}
@Override
public Object getElement() {
return m_reports.get(m_index).getName();
}
@Override
public String getKey() {
return m_reports.get(m_index).getKey();
}
@Override
public boolean next() {
m_index++;
return (m_reports.size() > m_index);
}
}