Class TaskJuggler::TableReport
In: lib/taskjuggler/reports/TableReport.rb
Parent: ReportBase

This is base class for all types of tabular reports. All tabular reports are converted to an abstract (output independent) intermediate form first, before the are turned into the requested output format.

Methods

Attributes

legend  [R] 

Public Class methods

Returns the default column title for the columns id.

[Source]

# File lib/taskjuggler/reports/TableReport.rb, line 150
    def TableReport::defaultColumnTitle(id)
      # Return an empty string for some special columns that don't have a fixed
      # title.
      specials = %w( chart hourly daily weekly monthly quarterly yearly)
      return '' if specials.include?(id)

      # Return the title for build-in hardwired columns.
      @@propertiesById.include?(id) ? @@propertiesById[id][0] : nil
    end

Generate a new TableReport object.

[Source]

# File lib/taskjuggler/reports/TableReport.rb, line 85
    def initialize(report)
      super
      @report.content = self

      # Reference to the intermediate representation.
      @table = nil
      # The table is generated row after row. We need to hold some computed
      # values that are specific to certain columns. For that we use a Hash of
      # ReportTableColumn objects.
      @columns = { }

      @legend = ReportTableLegend.new

    end

Public Instance methods

Return the alignment of the column based on the colId or the attributeType.

[Source]

# File lib/taskjuggler/reports/TableReport.rb, line 174
    def alignment(colId, attributeType)
      if @@propertiesById.has_key?(colId)
        return @@propertiesById[colId][2]
      elsif @@propertiesByType.has_key?(attributeType)
        return @@propertiesByType[attributeType][1]
      else
        :center
      end
    end

This function returns true if the values for the colId column need to be calculated.

[Source]

# File lib/taskjuggler/reports/TableReport.rb, line 186
    def calculated?(colId)
      return @@propertiesById.has_key?(colId)
    end

[Source]

# File lib/taskjuggler/reports/TableReport.rb, line 100
    def generateIntermediateFormat
      super
    end

Return if the column values should be indented based on the colId or the propertyType.

[Source]

# File lib/taskjuggler/reports/TableReport.rb, line 162
    def indent(colId, propertyType)
      if @@propertiesById.has_key?(colId)
        return @@propertiesById[colId][1]
      elsif @@propertiesByType.has_key?(propertyType)
        return @@propertiesByType[propertyType][0]
      else
        false
      end
    end

This functions returns true if the values for the col_id column are scenario specific.

[Source]

# File lib/taskjuggler/reports/TableReport.rb, line 192
    def scenarioSpecific?(colId)
      if @@propertiesById.has_key?(colId)
        return @@propertiesById[colId][3]
      end
      return false
    end

[Source]

# File lib/taskjuggler/reports/TableReport.rb, line 199
    def supportedColumns
      @@propertiesById.keys
    end

Convert the table into an Array of Arrays. It has one Array for each line. The nested Arrays have one String for each column.

[Source]

# File lib/taskjuggler/reports/TableReport.rb, line 145
    def to_csv
      @table.to_csv
    end

Turn the TableReport into an equivalent HTML element tree.

[Source]

# File lib/taskjuggler/reports/TableReport.rb, line 105
    def to_html
      html = []

      html << XMLComment.new("Dynamic Report ID: " +
                             "#{@report.project.reportContexts.last.
                                dynamicReportId}")
      html << rt_to_html('header')
      html << (tableFrame = generateHtmlTableFrame)

      # Now generate the actual table with the data.
      tableFrame << generateHtmlTableRow do
        td = XMLElement.new('td')
        td << @table.to_html
        td
      end

      # Embedd the caption as RichText into the table footer.
      if a('caption')
        tableFrame << generateHtmlTableRow do
          td = XMLElement.new('td')
          td << (div = XMLElement.new('div', 'class' => 'tj_table_caption'))
          a('caption').sectionNumbers = false
          div << a('caption').to_html
          td
        end
      end

      # The legend.
      tableFrame << generateHtmlTableRow do
        td = XMLElement.new('td')
        td << @legend.to_html
        td
      end

      html << rt_to_html('footer')
      html
    end

Protected Instance methods

In case the user has not specified the report period, we try to fit all the tasks in and add an extra 5% time at both ends for some specific type of columns. scenarios is a list of scenario indexes. columnDef is a reference to the TableColumnDefinition object describing the current column.

[Source]

# File lib/taskjuggler/reports/TableReport.rb, line 210
    def adjustColumnPeriod(columnDef, tasks = [], scenarios = [])
      # If we have user specified dates for the report period or the column
      # period, we don't adjust the period. This flag is used to mark if we
      # have user-provided values.
      doNotAdjustStart = false
      doNotAdjustEnd = false

      # Determine the start date for the column.
      if columnDef.start
        # We have a user-specified, column specific start date.
        rStart = columnDef.start
        doNotAdjustStart = true
      else
        # Use the report start date.
        rStart = a('start')
        doNotAdjustStart = true if rStart != @project['start']
      end

      if columnDef.end
        rEnd = columnDef.end
        doNotAdjustEnd = true
      else
        rEnd = a('end')
        doNotAdjustEnd = true if rEnd != @project['end']
      end
      origStart = rStart
      origEnd = rEnd

      # Save the unadjusted dates to the columns Hash.
      @columns[columnDef] = TableReportColumn.new(rStart, rEnd)

      # If the task list is empty or the user has provided a custom start or
      # end date, we don't touch the report period.
      return if tasks.empty? || scenarios.empty? ||
                (doNotAdjustStart && doNotAdjustEnd)

      # Find the start date of the earliest tasks included in the report and
      # the end date of the last included tasks.
      rStart = rEnd = nil
      scenarios.each do |scenarioIdx|
        tasks.each do |task|
          date = task['start', scenarioIdx] || @project['start']
          rStart = date if rStart.nil? || date < rStart
          date = task['end', scenarioIdx] || @project['end']
          rEnd = date if rEnd.nil? || date > rEnd
        end
      end

      # We want to add at least 5% on both ends.
      margin = 0
      minWidth = rEnd - rStart + 1
      case columnDef.id
      when 'chart'
        # In case we have a 'chart' column, we enforce certain minimum width
        # The following table contains an entry for each scale. The entry
        # consists of the triple 'seconds per unit', 'minimum width units'
        # and 'margin units'. The minimum with does not include the margins
        # since they are always added.
        mwMap = {
          'hour' =>    [ 60 * 60,            18, 2 ],
          'day' =>     [ 60 * 60 * 24,       18, 2 ],
          'week' =>    [ 60 * 60 * 24 * 7,    6, 1 ],
          'month' =>   [ 60 * 60 * 24 * 31,  10, 1 ],
          'quarter' => [ 60 * 60 * 24 * 90,   6, 1 ],
          'year' =>    [ 60 * 60 * 24 * 365,  4, 1 ]
        }
        entry = mwMap[columnDef.scale]
        raise "Unknown scale #{columnDef.scale}" unless entry
        margin = entry[0] * entry[2]
        # If the with determined by start and end dates of the task is below
        # the minimum width, we increase the width to the value provided by
        # the table.
        minWidth = entry[0] * entry[1] if minWidth < entry[0] * entry[1]
      when 'hourly', 'daily', 'weekly', 'monthly', 'quarterly', 'yearly'
        # For the calendar columns we use a similar approach as we use for
        # the 'chart' column.
        mwMap = {
          'hourly' =>    [ 60 * 60,            18, 2 ],
          'daily' =>     [ 60 * 60 * 24,       18, 2 ],
          'weekly' =>    [ 60 * 60 * 24 * 7,    6, 1 ],
          'monthly' =>   [ 60 * 60 * 24 * 31,  10, 1 ],
          'quarterly' => [ 60 * 60 * 24 * 90,   6, 1 ],
          'yearly' =>    [ 60 * 60 * 24 * 365,  4, 1 ]
        }
        entry = mwMap[columnDef.id]
        raise "Unknown scale #{columnDef.id}" unless entry
        margin = entry[0] * entry[2]
        minWidth = entry[0] * entry[1] if minWidth < entry[0] * entry[1]
      else
        doNotAdjustStart = doNotAdjustEnd = true
      end

      unless doNotAdjustStart && doNotAdjustEnd
        if minWidth > (rEnd - rStart + 1)
          margin = (minWidth - (rEnd - rStart + 1)) / 2
        end

        rStart -= margin
        rEnd += margin

        # Save the adjusted dates to the columns Hash.
        @columns[columnDef] = TableReportColumn.new(
          doNotAdjustStart ? origStart : rStart,
          doNotAdjustEnd ? origEnd : rEnd)
      end
    end

Generate a ReportTableLine for each of the accounts in accountList. If scopeLine is defined, the generated account lines will be within the scope this resource line.

[Source]

# File lib/taskjuggler/reports/TableReport.rb, line 376
    def generateAccountList(accountList, lineOffset, mode)
      # Get the current Query from the report context and create a copy. We
      # are going to modify it.
      accountList.query = query = @project.reportContexts.last.query.dup
      accountList.sort!

      # The primary line counter. Is not used for enclosed lines.
      no = lineOffset
      # The scope line counter. It's reset for each new scope.
      lineNo = lineOffset
      # Init the variable to get a larger scope
      line = nil
      accountList.each do |account|
        query.property = account

        no += 1
        Log.activity if lineNo % 10 == 0
        lineNo += 1
        a('scenarios').each do |scenarioIdx|
          query.scenarioIdx = scenarioIdx
          # Generate line for each account.
          line = ReportTableLine.new(@table, account, nil)

          line.no = no
          line.lineNo = lineNo
          line.subLineNo = @table.lines
          setIndent(line, a('accountroot'), accountList.treeMode?)

          # Generate a cell for each column in this line.
          a('columns').each do |columnDef|
            query.attributeId = columnDef.id
            next unless generateTableCell(line, columnDef, query)
          end
        end
      end
      lineNo
    end

Generates cells for the table header. columnDef is the TableColumnDefinition object that describes the column. Based on the id of the column different actions need to be taken to generate the header text.

[Source]

# File lib/taskjuggler/reports/TableReport.rb, line 320
    def generateHeaderCell(columnDef)
      rStart = @columns[columnDef].start
      rEnd = @columns[columnDef].end

      case columnDef.id
      when 'chart'
        # For the 'chart' column we generate a GanttChart object. The sizes are
        # set so that the lines of the Gantt chart line up with the lines of the
        # table.
        gantt = GanttChart.new(a('now'),
                               a('weekStartsMonday'), self)
        gantt.generateByScale(rStart, rEnd, columnDef.scale)
        # The header consists of 2 lines separated by a 1 pixel boundary.
        gantt.header.height = @table.headerLineHeight * 2 + 1
        # The maximum width of the chart. In case it needs more space, a
        # scrollbar is shown or the chart gets truncated depending on the output
        # format.
        gantt.viewWidth = columnDef.width ? columnDef.width : 450
        column = ReportTableColumn.new(@table, columnDef, '')
        column.cell1.special = gantt
        column.cell2.hidden = true
        column.scrollbar = gantt.hasScrollbar?
        @table.equiLines = true
      when 'hourly'
        genCalChartHeader(columnDef, rStart.midnight, rEnd, :sameTimeNextHour,
                          :weekdayAndDate, :hour)
      when 'daily'
        genCalChartHeader(columnDef, rStart.midnight, rEnd, :sameTimeNextDay,
                          :monthAndYear, :day)
      when 'weekly'
        genCalChartHeader(columnDef,
                          rStart.beginOfWeek(a('weekStartsMonday')), rEnd,
                          :sameTimeNextWeek, :monthAndYear, :day)
      when 'monthly'
        genCalChartHeader(columnDef, rStart.beginOfMonth, rEnd,
                          :sameTimeNextMonth, :year, :shortMonthName)
      when 'quarterly'
        genCalChartHeader(columnDef, rStart.beginOfQuarter, rEnd,
                          :sameTimeNextQuarter, :year, :quarterName)
      when 'yearly'
        genCalChartHeader(columnDef, rStart.beginOfYear, rEnd, :sameTimeNextYear,
                          nil, :year)
      else
        # This is the most common case. It does not need any special treatment.
        # We just set the pre-defined or user-defined column title in the first
        # row of the header. The 2nd row is not visible.
        column = ReportTableColumn.new(@table, columnDef, columnDef.title)
        column.cell1.rows = 2
        column.cell2.hidden = true
        column.cell1.width = columnDef.width if columnDef.width
      end
    end

Generate a ReportTableLine for each of the resources in resourceList. In case taskList is not nil, it also generates the nested task lines for each task that the resource is assigned to. If scopeLine is defined, the generated resource lines will be within the scope this task line.

[Source]

# File lib/taskjuggler/reports/TableReport.rb, line 476
    def generateResourceList(resourceList, taskList, scopeLine)
      # Get the current Query from the report context and create a copy. We
      # are going to modify it.
      resourceList.query = query = @project.reportContexts.last.query.dup
      query.scopeProperty = scopeLine ? scopeLine.property : nil
      resourceList.sort!

      # The primary line counter. Is not used for enclosed lines.
      no = 0
      # The scope line counter. It's reset for each new scope.
      lineNo = scopeLine ? scopeLine.lineNo : 0
      # Init the variable to get a larger scope
      line = nil
      resourceList.each do |resource|
        # Get the current Query from the report context and create a copy. We
        # are going to modify it.
        query.property = resource
        query.scopeProperty = scopeLine ? scopeLine.property : nil

        no += 1
        Log.activity if lineNo % 10 == 0
        lineNo += 1
        a('scenarios').each do |scenarioIdx|
          query.scenarioIdx = scenarioIdx
          # Generate line for each resource.
          line = ReportTableLine.new(@table, resource, scopeLine)

          line.no = no unless scopeLine
          line.lineNo = lineNo
          line.subLineNo = @table.lines
          setIndent(line, a('resourceroot'), resourceList.treeMode?)

          # Generate a cell for each column in this line.
          a('columns').each do |column|
            query.attributeId = column.id
            next unless generateTableCell(line, column, query)
          end
        end

        if taskList
          # If we have a taskList we generate nested lines for each of the
          # tasks that the resource is assigned to and pass the user-defined
          # filter.
          taskList.setSorting(a('sortTasks'))
          assignedTaskList = filterTaskList(taskList, resource,
                                            a('hideTask'), a('rollupTask'),
                                            a('openNodes'))
          assignedTaskList.sort!
          lineNo = generateTaskList(assignedTaskList, nil, line)
        end
      end
      lineNo
    end

Generate a ReportTableLine for each of the tasks in taskList. In case resourceList is not nil, it also generates the nested resource lines for each resource that is assigned to the particular task. If scopeLine is defined, the generated task lines will be within the scope this resource line.

[Source]

# File lib/taskjuggler/reports/TableReport.rb, line 419
    def generateTaskList(taskList, resourceList, scopeLine)
      # Get the current Query from the report context and create a copy. We
      # are going to modify it.
      taskList.query = query = @project.reportContexts.last.query.dup
      query.scopeProperty = scopeLine ? scopeLine.property : nil
      taskList.sort!

      # The primary line counter. Is not used for enclosed lines.
      no = 0
      # The scope line counter. It's reset for each new scope.
      lineNo = scopeLine ? scopeLine.lineNo : 0
      # Init the variable to get a larger scope
      line = nil
      taskList.each do |task|
        # Get the current Query from the report context and create a copy. We
        # are going to modify it.
        query.property = task
        query.scopeProperty = scopeLine ? scopeLine.property : nil

        no += 1
        Log.activity if lineNo % 10 == 0
        lineNo += 1
        a('scenarios').each do |scenarioIdx|
          query.scenarioIdx = scenarioIdx
          # Generate line for each task.
          line = ReportTableLine.new(@table, task, scopeLine)

          line.no = no unless scopeLine
          line.lineNo = lineNo
          line.subLineNo = @table.lines
          setIndent(line, a('taskroot'), taskList.treeMode?)

          # Generate a cell for each column in this line.
          a('columns').each do |columnDef|
            query.attributeId = columnDef.id
            next unless generateTableCell(line, columnDef, query)
          end
        end

        if resourceList
          # If we have a resourceList we generate nested lines for each of the
          # resources that are assigned to this task and pass the user-defined
          # filter.
          resourceList.setSorting(a('sortResources'))
          assignedResourceList = filterResourceList(resourceList, task,
              a('hideResource'), a('rollupResource'), a('openNodes'))
          assignedResourceList.sort!
          lineNo = generateResourceList(assignedResourceList, nil, line)
        end
      end
      lineNo
    end

[Validate]