Automatic Project Map generator, Tree of multiple databases (script)

This working script automates the creation of hierarchical project maps in Fibery, dynamically organizing project data into a structured document. It generates a visual representation of project hierarchies in the Description field of an entity Page, and an additional automation attaches a PDF version.

Use Cases:

The script shared is more like a general purpose script, that I adapted in several ways in other scripts to accomodate our needs. But I think for everyone still useful for snapshot creation for:

  1. Regulatory compliance and auditing
  2. Historical record-keeping
  3. Decision-making support
  4. Sharing with various external stakeholders

The video shows 4 buttons, which are 4 slightly different tree displays

Compact, Clean Compact, Spacious, Clean Spacious.

// Configuration Module. Replace the Space/Database names with your ones.
const config = {
    entities: {
        1: { type: 'YO/Project', childField: 'Goals', indent: '' },
        2: { type: 'YO/Goal', childField: 'Stones', indent: ' └─ ' },
        3: { type: 'YO/Stone', childField: 'Tasks', indent: '        └─ ' },
        4: { type: 'YO/Task', childField: 'Sub Tasks', indent: '               └─ ' },
        5: { type: 'YO/Task', childField: 'Sub Tasks', indent: '                      └─ ' },
        6: { type: 'YO/Task', childField: 'Sub Tasks', indent: '                             └─ ' }
    },
    pageType: 'YO/Page'
};

// Initialization of the Fibery API service
const fibery = context.getService('fibery');

// Function to fetch entity type IDs
async function fetchEntityTypeIDs(fibery, entityNames) {
    const schema = await fibery.getSchema();
    const ids = {};
    entityNames.forEach(entityName => {
        const entityType = schema['typeObjects'].find(obj => obj.name === entityName);
        if (entityType) {
            ids[entityName] = entityType.id;
        } else {
            console.error(`Entity type '${entityName}' not found in schema.`);
        }
    });
    return ids;
}

// Function to initialize entity configurations with type IDs
async function initializeEntityConfigs(fibery, config) {
    const entityNames = Object.values(config.entities).map(entity => entity.type);
    const ids = await fetchEntityTypeIDs(fibery, entityNames);
    Object.keys(config.entities).forEach(key => {
        config.entities[key].typeId = ids[config.entities[key].type];
    });
}

// Recursive function to append entity content
async function appendEntityContent(contentArray, entity, level, config) {
    const entityConfig = config.entities[level];
    if (entityConfig.indent) {
        contentArray.push({
            type: 'text',
            text: entityConfig.indent
        });
    }

    contentArray.push({
        type: 'entity',
        attrs: {
            id: entity.id,
            text: entity.Name,
            typeId: entityConfig.typeId
        }
    }, {
        type: 'hard_break'
    });

    console.log(`Level ${level}: Processing ${entity.Name}`);
    if (entityConfig.childField) {
        console.log(`Fetching children for ${entity.Name} at level ${level}`);
        const children = await fibery.getEntityById(entityConfig.type, entity.id, [entityConfig.childField]);
        if (children && children[entityConfig.childField] && children[entityConfig.childField].length > 0) {
            const childPromises = children[entityConfig.childField].map(child => {
                const childContentArray = [];
                return appendEntityContent(childContentArray, child, level + 1, config).then(() => childContentArray);
            });
            const childContents = await Promise.all(childPromises);
            childContents.forEach(childContent => {
                contentArray.push(...childContent);
            });
        } else {
            console.log(`No children found for ${entity.Name} at level ${level}`);
        }
    }
}

// Main function to generate the project map
async function generateProjectMap() {
    try {
        await initializeEntityConfigs(fibery, config);
        const currentProjectEntity = args.currentEntities[0];
        console.log('Fetching Project Entity...');
        let projectEntity = await fibery.getEntityById(config.entities[1].type, currentProjectEntity.id, ['Name', 'Pages', config.entities[1].childField]);

        console.log('Creating new Page Entity...');
        let pageEntity = await fibery.createEntity(config.pageType, {
            'Name': `Project Map for ${projectEntity.Name}`,
            'Project': projectEntity.id
        });

        let projectMapContent = [{ type: 'paragraph', content: [] }];
        await appendEntityContent(projectMapContent[0].content, projectEntity, 1, config);

        console.log('Setting Document Content...');
        if (projectMapContent[0].content.length > 0) {
            await fibery.setDocumentContent(pageEntity.Description.Secret, JSON.stringify({ doc: { type: 'doc', content: projectMapContent } }), 'json');
        }

        console.log('Linking Page to Project...');
        await fibery.addCollectionItem(config.entities[1].type, projectEntity.id, 'Pages', pageEntity.id);
    } catch (error) {
        console.error('Error generating project map:', error);
    }
}

// Execute the generation of the project map
await generateProjectMap();

Script Explanation

Configuration Module

  • config Object: Contains configuration details for the entity types and their relationship fields. This includes the type of each entity level, the child field to navigate through the hierarchy, and the indentation for display purposes.
  • pageType: Specifies the type of page that will be created in Fibery for displaying the project map.

Initialization

  • Fibery API Service: Initializes a connection to the Fibery API.

Functions

  • fetchEntityTypeIDs(): Fetches the IDs for specified entity types from Fibery’s schema. This is critical for ensuring that API calls refer to the correct entity types by their unique identifiers.
  • initializeEntityConfigs(): Integrates the fetched type IDs into the configuration, preparing the entity settings with actual IDs from the Fibery system to be used in later operations.

Recursive Function for Content Generation

  • appendEntityContent():
    • Appends content related to an entity in a structured format (indentation, entity details).
    • Recursively handles child entities to build a nested structure. This function generates content for each entity, adding necessary formatting and recursively processing any children entities to map the entire hierarchy.
    • Manages parallel asynchronous operations to fetch and process child entities efficiently.

Main Execution Function

  • generateProjectMap():
    • Calls initializeEntityConfigs() to set up entity configurations with actual type IDs.
    • Retrieves the main project entity based on provided arguments (args.currentEntities[0]).
    • Creates a new page in Fibery named appropriately to reflect it is a project map.
    • Calls appendEntityContent() to populate the project map with detailed information about the project and its hierarchy.
    • Saves this content to the newly created page and links the page to the project for easy access.
    • Handles exceptions and logs errors to help diagnose issues during the map generation process.

Execution

  • The script concludes with an explicit call to generateProjectMap(), triggering the entire process when the script is run.
4 Likes

I feel a bit dumb to ask; but why not just embed the view in the rich text field?

I’m very impressed by your script but besides the PDF I can’t think of a use case when to actually use those buttons instead of an embedded view.

1 Like

Good question, and I actually hope that Fibery views will evolve to cover at least some of the use cases below.

Although I work with complex collaboration networks that design organizational patterns and decision making models, so most of the reasons mentioned below will not be a motivation for simple use of fibery. Essentially, its for making snapshots of states and relationships in rich text fields, which is not possible in views, since views are always displaying the current states and relationships.

Some background in how I found maps of relationships using scripts necessary:

1. Documentation and Archival

  • Snapshot Capability: Captures exact project states at specific times for compliance and auditing, which dynamic views cannot lock in time.
  • Version Control: Tracks detailed changes over time, providing a clear audit trail not possible with always-current views.
  • Historical Comparisons: Facilitates side-by-side analysis of project stages over time, essential for assessing progress and making strategic adjustments.

2. Sharing and Communication

  • Ease of Sharing: Simplifies the process of sharing project details with external stakeholders who lack access to Fibery, which dynamic views cannot easily achieve. The public sharing feature of Fibery is pretty limited.
  • Offline Accessibility: Ensures project details are available offline, supporting discussions in environments without internet connectivity.
  • Simplified Stakeholder Engagement: Presents a clear, straightforward project overview for stakeholders unfamiliar with navigating complex project management tools.

3. Customization and Usability

  • Customized Formatting and Indentation: Allows for bespoke report formats that highlight specific project elements, offering a visual clarity that embedded views might not match due to layout constraints.
  • Focused Content Delivery: Provides targeted information essential for specific meetings or decisions, avoiding the complexity and potential clutter of comprehensive views.

4. Operational and Strategic Reviews

  • Dynamic Configuration: Enables rapid adjustment of displayed project information during live discussions, facilitating strategic planning and decision-making.
  • Multiple Configurations Exploration: Allows for quick visualization of different structural configurations without altering the core project data, useful for hypothetical scenario planning.

5. Integration and Automation

  • Integration with Existing Workflows: Seamlessly triggers updates and creates documents aligned with specific workflow stages, ensuring timely dissemination of current project maps.
  • Enhanced Customization for Reporting: Generates tailored reports for various audiences, focusing on aspects like risks, timelines, or dependencies, which can be cumbersome with embedded views.

6. Access and Control

  • Enhanced Access Control: Provides refined control over document accessibility, crucial for managing sensitive information and ensuring confidentiality.
  • Reduced Training and Technical Requirement: Reduces the need for in-depth training on Fibery’s interface, offering an accessible way to engage with project data.
2 Likes