Beginning API Discovery (or understanding the RootServices document)


Photo by Felix Mittermeier on Unsplash

Now that we’ve covered some of the basics on what we mean when we say APIs (link to prior post), it’s time to get into the power of ELM thru its API discovery process.

Many of the APIs on a server are exposed thru API calls.  The foundation of this discovery mechanism is the “RootServices” document of the ELM application.  Each ELM application has its own RootServices document. I’ll be focusing on four of the ELM applications, specifically:

  • JTS – Jazz Team Server
  • CCM – Configuration and Change Management – AKA – Engineering Workflow Management
  • QM – Quality Management – AKA – Engineering Test Management
  • RM – Requirements Management – AKA Engineering Requirements Management or DOORS Next

For the purpose of this series of blog posts, I will be using a locally hosted server named https://elmwb.com on port 9443. You will need to replace this server name and port with the server information of your own.

The RootServices API is publicly addressable; it doesn’t require any authentication.  The reason behind this is to ensure that you can discover the services for authentication, and so that servers can query each other regarding what services are available.  Most APIs that are exposed in the RootServices do require authentication.   I will have two separate blog posts which all address authentication, one covering OAuth1.0a and one covering the use of a Bearer token.

RootServices API

The API for accessing the RootServices document is simply:

GET https://<<SERVER>>/<<APPLICATION>>/rootservices

The API also requires you to include at least one of the following headers in order to define the format of the RootServices document:

	accept:application/rdf+xml
	accept:application/json
	accept:text/turtle

If you use CURL the API call would be:

curl --location --request GET ‘https://elmwb.com:9443/rm/rootservices' \
	--header 'accept: application/rdf+xml'

The flexibility of the response body allows you to introspect the contents in an appropriate manner for your specific needs.  One key difference to note in the formats is that both RDF and Turtle formats provide descriptors to simplify the contents; json does not have descriptors.  In RDF, these descriptors are identified with “xmlns” (or xml name space), while in turtle they are identified with @prefix.  Understanding the rest of the differences between these formats is up to you. More information can be found at:

The following images show the beginning of the RootServices document in the above three formats:

application/rdf+xml
application/json
text/turtle

Let’s look at the overall structure and content of the RootServices document and how it will help us for future APIs.  I will be using the rdf+xml format for the rest of this post.

There are three major sections in the RootServices document:

  1. Name Spaces – the name spaces identify the domain and/or process that governs a specific resource.  Each name space is provided with a short identifier which is used as a prefix for defining the full URI which describes a resource.  For example the identifier xmlns:dc describes the “Dublin Core” term as defined at “http://purl.org/dc/terms/“.  This prefix is then used in the definition of resource called <dc:title>.  The full definition of the title can be found at http://purl.org/dc/terms#title .
Dublin Core term example
  • Understanding the details about “Dublin Core” is not critical to the understanding of the RootServices document; it provides a simple way of explaining the name space concept. If you want learn more about “Dublin Core” you can check out their website at https://dublincore.org.
  1. Comments – In the rdf+xml format these lines are identified as <!— —> and are informational only.
  1. Resources – This section describes the name spaces for the server. By placing the service in this RootServices document this can be considered public APIs.  The name space prefix defined earlier in the RootServices document can provide you context about which aspect of the server the specific service addresses.  Some general guidelines include:
  • jd = Jazz Discovery
  • jfs = Jazz Foundation 
  • jdb = Jazz Dashboard
  • ju = Jazz UI elements
  • jp06 = Jazz Process (release 0.6)
  • jp = Jazz Process (release 1.0)
  • oslc = Open Services for Lifecycle Collaboration – this is the Oasis based standard for services across lifecycle based applications.  ELM is both a provider and consumer of OSLC services.  For more information on OSLC and Oasis see: https://open-services.net and https://www.oasis-open.org/ .
  • oslc_XX = OSLC services for a specific application, for example rm = requirements management, cm = change management, etc.
  • rm = Requirements Management, these are DOORS Next specific services
  • trs = Tracked Resource Set version 1.x
  • trs2 = Tracked Resource Set version 2

A detailed (yet somewhat dated) description of the RootServices document can be found on jazz.net at https://jazz.net/wiki/bin/view/Main/RootServicesSpec.

Next, we focus on a few key sections in the RootServices document which will help us discover more APIs as we move forward:

First, after the list of name spaces, we see the Title of the server whose RootServices we are accessing and a detailed description of the server.  They are all in the dc name space, i.e. Dublin Core.

<dc:title xml:lang="en">Doors Next</dc:title>
<dc:description>This application provides the capabilities to create and manage requirements and trace them to modeling, testing, and change and configuration management. You can define, elicit, capture, elaborate, discuss, and review requirements and supporting artifacts.</dc:description>

Second, is a list of common services for the Jazz server and application we are on. Included in this section are those services that are necessary for authentication when using our APIs.

If you are utilizing OAuth1.0a to access the APIs, you should look at the jfs services with include oauth in their name.  On my server, the following services are exposed:

<jfs:oauthDomain>https://elmwb.com:9443/jts,https://elmwb.com:9443/rm</jfs:oauthDomain>
<jfs:oauthRealmName>Jazz</jfs:oauthRealmName>
<jfs:oauthAccessTokenUrl rdf:resource="https://elmwb.com:9443/jts/oauth-access-token" />
<jfs:oauthApprovalModuleUrl rdf:resource="https://elmwb.com:9443/jts/_ajax-modules/com.ibm.team.repository.AuthorizeOAuth" />
<jfs:oauthExpireTokenUrl rdf:resource="https://elmwb.com:9443/jts/oauth-expire-token" />
<jfs:oauthRequestConsumerKeyUrl rdf:resource="https://elmwb.com:9443/jts/oauth-request-consumer" />
<jfs:oauthRequestTokenUrl rdf:resource="https://elmwb.com:9443/jts/oauth-request-token" />
<jfs:oauthUserAuthorizationUrl rdf:resource="https://elmwb.com:9443/jts/oauth-authorize" />

We see three key end points which are necessary for a OAuth1.0a process (which I will cover in detail in another blog post) – oauthAccessTokenUrl, oauthRequestTokenUrl, and oauthUserAuthorizationUrl.  These three URLs are needed to address the three steps that a OAuth authentication that I will cover in another blog post.  

Third, is a list of application specific services.  On our sample server, which is Doors Next application server, we see the following list of services.

<!-- Applications may add any services they provide here -->
<oslc_rm:rmServiceProviders rdf:resource="https://elmwb.com:9443/rm/oslc_rm/catalog" />
    <oslc_rm:majorVersion>7</oslc_rm:majorVersion>
    <oslc_rm:version>7.0.2</oslc_rm:version>
    <oslc_rm:buildVersion>7.0.2</oslc_rm:buildVersion>
    <oslc_config:cmServiceProviders rdf:resource="https://elmwb.com:9443/rm/oslc_config"/>
    <rm:glossaryTermsQuery rdf:resource="https://elmwb.com:9443/rm/glossary/termslookup" />
    <rm:components rdf:resource="https://elmwb.com:9443/rm/cm/component/ldpc" />
    <rm:projectAreas rdf:resource="https://elmwb.com:9443/rm/projects" />
    <rm:cmpProjectAreas rdf:resource="https://elmwb.com:9443/rm/rm-projects" />
    <rm:rrcExtensions>0.1.3-0.1.5</rm:rrcExtensions>
<!-- The admin Web UI service should be uncommented in applications -->
    <jfs:adminWebUI rdf:resource="https://elmwb.com:9443/rm/admin" />
<!-- The registration handler service should be uncommented for application  that do not supply their own -->
    <jd:registration rdf:resource="https://elmwb.com:9443/rm/service/com.ibm.team.repository.service.internal.setup.IRegistrationHandlerService" />
<!--  Process Component Tracked Resource Set 2.0 Provider -->
    <rm:trackedResourceSetProvider>
        <trs2:TrackedResourceSet>
            <trs2:trackedResourceSet rdf:resource="https://elmwb.com:9443/rm/process-trs2" />
            <dc:title>TRS 2.0 for DOORS Next Process Resources</dc:title>
            <dc:description>TRS 2.0 feed for process resources in DOORS Next</dc:description>
            <dc:type     rdf:resource="http://jazz.net/ns/process#" />
            <oslc:domain rdf:resource="http://jazz.net/ns/process#" />
        </trs2:TrackedResourceSet>
    </rm:trackedResourceSetProvider>
<!-- End of application-specific services -->

As you can see this server has OSLC and RM specific services.  We also see that this server is a TRS 2.0 provider. 

Key for our discovery of APIs, we would want to look at the oslc:rmServiceProviders.  A GET to this API will provide us with a catalog of those objects that provide services.  In the case of this server it will be a catalog of the Project areas on our server, along with the services they provide.

Let’s do a quick GET call to the API identified in oslc:rmServiceProviders.  If you are trying this via a browser, you will get prompted for a log in, this tells us that the API needs to be authenticated.  Go ahead and log in and look at the results.  On my server I have setup three Project areas, and it will provide us with the following response:

<rdf:RDF
 xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
 xmlns:dcterms="http://purl.org/dc/terms/"
 xmlns:jp="http://jazz.net/xmlns/prod/jazz/process/1.0/"
 xmlns:jazzDisc="http://jazz.net/ns/discovery#"
 xmlns:oslc="http://open-services.net/ns/core#">

 <oslc:ServiceProviderCatalog rdf:about="https://elmwb.com:9443/rm/oslc_rm/catalog">
    <dcterms:title rdf:parseType="Literal">RMCatalog</dcterms:title>
    <dcterms:publisher rdf:resource="https://elmwb.com:9443/rm/application-about" />
    <oslc:domain rdf:resource="http://open-services.net/ns/rm#" />
    <oslc:serviceProvider>
      <oslc:ServiceProvider rdf:about="https://elmwb.com:9443/rm/oslc_rm/_LgdUMIkaEey3TMAARolZjw/services.xml">
         <dcterms:title rdf:parseType="Literal">JKE Banking (Requirements Management)</dcterms:title>
         <jazzDisc:messageReceiver rdf:resource="https://elmwb.com:9443/rm/web/com/ibm/rdm/web/copyReceiver/CopyHandler.html"/>
         <jp:consumerRegistry rdf:resource="https://elmwb.com:9443/rm/process/project-areas/_LgdUMIkaEey3TMAARolZjw/links"/>
         <oslc:details rdf:resource="https://elmwb.com:9443/rm/process/project-areas/_LgdUMIkaEey3TMAARolZjw"/>
      </oslc:ServiceProvider>
    </oslc:serviceProvider>
    <oslc:serviceProvider>
      <oslc:ServiceProvider rdf:about="https://elmwb.com:9443/rm/oslc_rm/_VoWUwIkbEey3TMAARolZjw/services.xml">
         <dcterms:title rdf:parseType="Literal">Sample Project 1 (Requirements)</dcterms:title>
         <jazzDisc:messageReceiver rdf:resource="https://elmwb.com:9443/rm/web/com/ibm/rdm/web/copyReceiver/CopyHandler.html"/>
         <jp:consumerRegistry rdf:resource="https://elmwb.com:9443/rm/process/project-areas/_VoWUwIkbEey3TMAARolZjw/links"/>
         <oslc:details rdf:resource="https://elmwb.com:9443/rm/process/project-areas/_VoWUwIkbEey3TMAARolZjw"/>
       </oslc:ServiceProvider>
    </oslc:serviceProvider>
    <oslc:serviceProvider>
    <oslc:ServiceProvider rdf:about="https://elmwb.com:9443/rm/oslc_rm/_BVnHAImtEeyjbIIiaev_dg/services.xml">
       <dcterms:title rdf:parseType="Literal">Title test</dcterms:title>
       <jazzDisc:messageReceiver rdf:resource="https://elmwb.com:9443/rm/web/com/ibm/rdm/web/copyReceiver/CopyHandler.html"/>
       <jp:consumerRegistry rdf:resource="https://elmwb.com:9443/rm/process/project-areas/_BVnHAImtEeyjbIIiaev_dg/links"/>
       <oslc:details rdf:resource="https://elmwb.com:9443/rm/process/project-areas/_BVnHAImtEeyjbIIiaev_dg"/>
    </oslc:ServiceProvider>
  </oslc:serviceProvider>
</oslc:ServiceProviderCatalog>
</rdf:RDF>

I am not going to go through all the details right now, but just realize we have a list of all the project areas (or Service Providers) on this server.  We can actually execute a GET against a specific project area to get a list of supported services for that Project area.  This can be found in the <oslc:ServiceProvider > section.  But that’s for another blog post.

API types and the API Landing Page

When someone mentions APIs what do you think of?  Let’s look at the acronym and the discuss how I will be using it over the course or this blog series. 

API Blackboard

API stands for “Application Programming Interface”.  This term is pretty broad as it is a connection between computers, programs and software services. APIs can be pubic or private, supported or unsupported.  I will only be talking about Public / supported APIs within the scope of our ELM capabilities.

Accessing a specific API can be done via a SDK (software development kit) or via Rest APIs (Representational state transfer ). I will begin with focusing on Rest API.

IBM ELM API Landing page

Jim Ruehlin & Rosa Naranjo have created a landing page that consolidates all the existing API documentation available across jazz.net (link).  This page provides grouping of existing APIs (both Rest and SDK based APIs), along with how to guides.  One thing to notes is this page references “CLM”.  CLM stands for Collaborative Lifecycle Management ( this is a legacy name for our IBM Engineering Lifecycle Management applications). 

As a developer, we all have an innate definition of what an API is. An API is how we access some function of an application.  APIs are exposed in SDKs (Software Development Kits), compiler or language libraries, and services.  I am going to focus this set of blog posts on service based APIs.  ELM utilizes a standards based Service Oriented Architecture called OSLC.  OSLC or Open Services for Lifecycle Collaboration is specifically designed to address the software development process, utilizing a SOA approach. As such, the API end points and payload requirements are discoverable via services.  I’ll be going thru this discovery process over a few blog posts.

However, let’s start with a quick review of our existing API Landing page and the menu on the right hand side.  As you can see we have sections for basic concept, each of the applications and developing java applications.

OSLC

General OSLC and CLM API information – The foundation for many of our APIs in ELM is OSLC.  OSLC is an open standard for defining messages and APIs across various lifecycle processes.  Getting a good understanding of OSLC is key for many of the topics we will discuss going forward.

Jazz Foundation

Jazz Foundation – Many of the ELM applications are built on top of our Jazz platform.   This foundational component supports a set of common APIs that should be the same regardless of the application you are working with.

Managing Variants

Global Configuration Management – One of the most powerful features of IBM ELM is Global Configuration Management or GCM.  GCM allows you to assemble configurations from across ELM applications.  These Global Configs (GCs) can be reused in different versions or variants of software and product lines. GCM supports the OSLC APIs, a set of Rest API and a Client Extension SDK.

DOORS Next – Requirements management (AKA – DNG) allows for capturing, tracing, analyzing, and managing changes to requirements. There are many different API models that are supported by DNG include OSLC, both client and server SDKs, and Rest APIs for Modules, Reports, ReqIf, and other features.

Rational Team Concert (EWM) – Workflow management allows teams to create and manage work plans across multiple development projects utilizing Agile, SAFe (Scaled Agile Framework for Enterprises), or custom processes. The API’s supported by EWM have a lot of coverage in blogs and other resources, including the work of Ralph Schoon. Ralph has been blogging about EWM APIs for over a decade.  Check out his blog at – https://rsjazz.wordpress.com/. EWM has support for OSLC, REST and SDK based APIs.

Rhapsody Model Manager – Architecture Management allows you to manager your models and code on the same configuration management server.  It also provides linkage between requirements and work items across ELM. It provides a set of REST APIs for accessing artifacts for reporting.

Rational Quality Manager (ETM) – Engineering Test Management enables test planning and management supporting test execution, results reporting, and integration with other testing execution applications.  Supporting both OSLC and REST based APIs, you may also create custom test automation adapters via APIs to integrate with third party test execution tools.

Reporting – ELM reporting is supported both by individual applications and across applications via the ELM Report Builder.  This section provides details on LQE (the Lifecycle Query Engine) and Report Builder, along with Reporting APIs for Requirements (DNG), Test (ETM), and Workflow(EWM).

Plain Java Client or Jazz APIs – The above sections will have links to the appropriate plain java client and other Jazz APIs.  This section provides a single place to get to them regardless of application supported.

If you’ve made it this far, you realize there really is a lot of API documentation and content for you to go thru.  But how can you digest it all?  In the next post I’ll take you thru the common entry point for understanding the APIs supported by a specific ELM application – the RootServices document!

Welcome to API Corner

“With Great Power comes Great Responsibility”. I remember first hearing this phrase in Spider Man comics.  But it has a much longer history than that.  Wikipedia indicates that this is actually an ancient phrase going back to the first century BC.  I find that IBM Engineering Lifecycle Management’s (IBM ELM’s)  incredible flexibility and power is a lot like this phrase.

You have great power in integrating the ELM capabilities with other tools and processes, but you are responsible for understanding a lot of basic concepts.

There’s a lot of existing content available on our API page, but it can be overwhelming if you are not familiar with the applications and some basic concept.  So over the course of this blog series I plan on going thru the major APIs across ELM applications, while also explaining some of the basic concepts behind how to understand our APIs.

Having said all this, let me introduce myself.  My name is Michael Rowe, and I’ve been at IBM since 1995.  In that time I’ve had many roles across development, product management, IBM research, services, and strategy.  In my spare time I develop iOS apps for fun, podcast on gaming and technology, swim and sing light opera.  You can follow those topics over at my personal blog – https://michaelrowe01.com. I joined IBM Rational in 2008 and focused on brand level strategy, looking at how we integrate with other IBM brands (like Tivoli), across the development lifecycle (Urban Code for DevOps, WorkLight for Mobile app development), and with business partners.  I moved to IBM research for a few years to help identify technologies which could and should be brought into products, coming back to the Watson IoT business unit in 2017 to help drive strategy.  I have worked with business partners for a few years and in 2021 came back to my technical roots by joining our ELM Architecture team with a focus on APIs.

I have experienced the same learning curve that many of you will be experiencing as you get started learning how to access and understand our APIs, and as such, we will take this journey together.  My personal approach to learning is to read, in order to understand a concept, and then attempt to validate what I read by performing a simple exercise.  Some of the concepts that we leverage in ELM may require multiple exercises.

My goal is to help you quickly get thru the concepts and find the APIs you need to better leverage the IBM ELM capabilities. Over time, I will be working closely with our product manager, Jim Ruehlin (link – https://jazz.net/wiki/bin/view/Main/JimRuehlin) , to address API documentation and clarity, but more on that in a future post.

I look forward to hearing from you…so let’s get started…

A potential plan for 2022

As I mentioned a while back, I took on a much more technical role in my day job recently. I am working on IBM’s “Engineering Lifecycle Management” product offering as a solution architect. My primary focus is on our reusable components, which includes many of our APIs.

APIs are the building blocks of modern applications, and ELM has had APIs since its foundation many years ago. Many of the APIs are build around the concept of Open-Services for Lifecycle Collaboration or OSLC. The OSLC standard utilizes RDF as the format for its payloads; which is unfamiliar to many people.

RDF allows for a semantically rich representation of data.

Swift Playgrounds

This weekend I wanted to see if I could make a very simple (and quick) iOS application utilizing Swift Playgrounds to read the “root services” document from any ELM application. I had created a iOS app for my own use to do this in the past, utilizing a Swift Package by Gregory Williams called serd-swift. This is a simple wrapper for the fast C library created by David Rollibard – serd.

Of course, like many ideas dreamed up in the shower, this project was not going to be as simple as I wanted. I started by creating a simple iOS app in Swift Playgrounds and adding in the above package. At this point Swift Playgrounds complained that it could not compile “C” code.

The funny thing is that Swift Playgrounds didn’t just come out and say that the Package was the problem, the message showed up as the error – “The target type, “Clang Target”, is not supported. The good news is, I used to write objective C on the Mac, so I knew this meant, the “C” programming Language.

I knew that in advance but had totally forgotten about it. So now my plan is to try to write the original serd library by David into a native Swift Package. This won’t be easy, and I plan to post updates on my blog as I progress.

Stay tuned, as this may take a while.

2022 – Code and Cloud

Well, made it thru to another year. 2022 seems to be starting with the same hope and courage of 2021, let’s hope it ends on a better note.

From Apple’s Xcode Cloud Website

I’ve been working on a Card Tracking app for a few years. It started as a project to hope me learn CoreData, I then pivoted to learn SwiftUI, and finally, I have pivoted again to use it to learn about Xcode Cloud. If you don’t know what that is, I highly recommend you check out the sessions from 2021’s WWDC.

Basically, Xcode Cloud is Apple’s CI/CD service for Xcode. (CI/CD is continuous integration / continuous delivery). This type of tool chain is used in many agile development shops to delivery new functions to customers as quickly as possible. The basic idea is, whenever new code is checked into your source repository the system will build, test, and delivery it as far as you define. In my case I would like to deliver a new build of my card tracking app to testers via TestFlight whenever I deliver new code.

This process is pretty easy, assuming you correctly define your build process. Currently I am able to have the system automatically build both my iOS and macOS apps and then attempt to post them to TestFlight. I say “Attempt” since for some reason the technique I use to auto-increment my builds is not being recognized. Xcode Cloud uses it’s own build number (CI_BUILD_NUMBER), and I can’t find a way to override it with the value I generate from my info.plist. It seems the only I can get this to work is to switch using a new release number; which would then allow for Xcode Cloud to take over the build number.

SwiftUI Fun and Bugs

Well I fixed the bug! The issue was the sequence of the code sequence between the ContactPicker and VStack within the Navigation View. I hadn’t realized that this was issue (Obviously). Here’s a video of the real way it should work.

As you can see the UI is now working as you would expect.

//
//  AddNewRecipientView.swift
//  Card Tracker
//
//  Created by Michael Rowe on 1/1/21.
//  Copyright © 2021 Michael Rowe. All rights reserved.
//

import SwiftUI
import SwiftUIKit
import ContactsUI
import Contacts
import CoreData

/* Thanks to @rlong405 and @nigelgee for initial guidance in getting past the early issues. */

struct AddNewRecipientView: View {
    @Environment(\.managedObjectContext) var moc
    @Environment(\.presentationMode) var presentationMode

    @State private var lastName: String = ""
    @State private var firstName: String = ""
    @State private var addressLine1: String = ""
    @State private var addressLine2: String = ""
    @State private var city: String = ""
    @State private var state: String = ""
    @State private var zip: String = ""
    @State private var country: String = ""

    @State var showPicker = false

    init() {
        let navBarApperance = UINavigationBarAppearance()
        navBarApperance.largeTitleTextAttributes = [
            .foregroundColor: UIColor.systemGreen,
            .font: UIFont(name: "ArialRoundedMTBold", size: 35)!]
        navBarApperance.titleTextAttributes = [
            .foregroundColor: UIColor.systemGreen,
            .font: UIFont(name: "ArialRoundedMTBold", size: 20)!]
        UINavigationBar.appearance().standardAppearance = navBarApperance
        UINavigationBar.appearance().scrollEdgeAppearance = navBarApperance
        UINavigationBar.appearance().compactAppearance = navBarApperance
    }

    var body: some View {
        NavigationView {
            GeometryReader { geomtry in
                ContactPicker(showPicker: $showPicker, onSelectContact: {contact in
                    firstName = contact.givenName
                    lastName = contact.familyName
                    if contact.postalAddresses.count > 0 {
                        if let addressString = (
                            ((contact.postalAddresses[0] as AnyObject).value(forKey: "labelValuePair")
                             as AnyObject).value(forKey: "value"))
                            as? CNPostalAddress {
                            // swiftlint:disable:next line_length
                            let mailAddress = CNPostalAddressFormatter.string(from: addressString, style: .mailingAddress)
                            addressLine1 = "\(addressString.street)"
                            addressLine2 = ""
                            city = "\(addressString.city)"
                            state = "\(addressString.state)"
                            zip = "\(addressString.postalCode)"
                            country = "\(addressString.country)"
                            print("Mail address is \n\(mailAddress)")
                        }
                    } else {
                        addressLine1 = "No Address Provided"
                        addressLine2 = ""
                        city = ""
                        state = ""
                        zip = ""
                        country = ""
                        print("No Address Provided")
                    }
                    self.showPicker.toggle()
                }, onCancel: nil)
                VStack {
                    Text("")
                    HStack {
                        VStack(alignment: .leading) {
                            TextField("First Name", text: $firstName)
                                .customTextField()
                        }
                        VStack(alignment: .leading) {
                            TextField("Last Name", text: $lastName)
                                .customTextField()
                        }
                    }
                    TextField("Address Line 1", text: $addressLine1)
                        .customTextField()
                    TextField("Address Line 2", text: $addressLine2)
                        .customTextField()
                    HStack {
                        TextField("City", text: $city)
                            .customTextField()
                            .frame(width: geomtry.size.width * 0.48)
                        Spacer()
                        TextField("ST", text: $state)
                            .customTextField()
                            .frame(width: geomtry.size.width * 0.18)
                        Spacer()
                        TextField("Zip", text: $zip)
                            .customTextField()
                            .frame(width: geomtry.size.width * 0.28)
                    }
                    TextField("Country", text: $country)
                        .customTextField()
                    Spacer()
                }
            }
            .padding([.leading, .trailing], 10 )
            .navigationTitle("Recipient")
            .navigationBarItems(trailing:
                                    HStack {
                Button(action: {
                    let contactsPermsissions = checkContactsPermissions()
                    if contactsPermsissions == true {
                        self.showPicker.toggle()
                    }
                }, label: {
                    Image(systemName: "person.crop.circle.fill")
                        .font(.largeTitle)
                        .foregroundColor(.green)
                })
                Button(action: {
                    saveRecipient()
                    self.presentationMode.wrappedValue.dismiss()
                }, label: {
                    Image(systemName: "square.and.arrow.down")
                        .font(.largeTitle)
                        .foregroundColor(.green)
                })
                Button(action: {
                    self.presentationMode.wrappedValue.dismiss()
                }, label: {
                    Image(systemName: "chevron.down.circle.fill")
                        .font(.largeTitle)
                        .foregroundColor(.green)
                })
            }
            )
        }
    }

    func saveRecipient() {
        print("Saving...")
        if firstName != "" {
            let recipient = Recipient(context: self.moc)
            recipient.firstName = firstName
            recipient.lastName = lastName
            recipient.addressLine1 = addressLine1.capitalized(with: NSLocale.current)
            recipient.addressLine2 = addressLine2.capitalized(with: NSLocale.current)
            recipient.state = state.uppercased()
            recipient.city = city.capitalized(with: NSLocale.current)
            recipient.zip = zip
            recipient.country = country.capitalized(with: NSLocale.current)
        }
        do {
            try moc.save()
        } catch let error as NSError {
            print("Save error: \(error), \(error.userInfo)")
        }
    }

    func checkContactsPermissions() -> Bool {
        let authStatus = CNContactStore.authorizationStatus(for: .contacts)
        switch authStatus {
        case .restricted:
            print("User cannot grant premission, e.g. parental controls are in force.")
            return false
        case .denied:
            print("User has denided permissions")
            // add a popup to say you have denied permissions
            return false
        case .notDetermined:
            print("you need to request authorization via the API now")
        case .authorized:
            print("already authorized")
        @unknown default:
            print("unknown error")
            return false
        }
        let store = CNContactStore()
        if authStatus == .notDetermined {
            store.requestAccess(for: .contacts) {success, error in
                if !success {
                    print("Not authorized to access contacts. Error = \(String(describing: error))")
                    exit(1)
                }
                print("Authorized")
            }
        }
        return true
    }
}

struct AddNewRecipientView_Previews: PreviewProvider {
    static var previews: some View {
        AddNewRecipientView()
            .environment(\.managedObjectContext, PersistentCloudKitContainer.persistentContainer.viewContext)
    }
}

Update on Holiday Card Tracker

A few years back I started an app that I was thinking of calling Christmas card tracker. The goal of the app was to learn Core Data. I spent time at WWDC working with a fellow attendee to fix problems I was having with Autolayout constraints, and got an early version working enough to where I could start capturing the cards my wife and I send out each year.

I then ran out of time and put it on the back burner. Along comes SwiftUI and I re-wrote the app from scratch, fixing a ton of problems, and along the way making it available for macOS, iPadOS and iOS, I even toyed with a watchOS version. It took me a few years of sporadic work, as I did a rewrite of my app Wasted Time, so that it could run on macOS and tvOS.

Well I am very close to releasing version 1.0, but have run into a very frustrating bug. When I go to add a new recipient of a card I give the user the option of searching their contacts list, or adding a new entry in the app. The Mac version of the screen can be seen here:

Add Recipient Screen.

As you can see, their are three icons. The first is save, the second is search (your contacts) and the the third is close the entry without a save. The problem is, you don’t see a cursor in the first input field – “First Name”. You can use a mouse or the tab key to move the cursor into the field. You can type and tab between fields; however, on the iPad or iPhone, you cannot touch the screen to start entering in the field. You actually can’t enter the cursor into any fields, without a mouse or keyboard.

I am hoping to resolve this issue, but I’ve been banging my head for some time. I’ve tried posting on Stack Overflow, and all I got was a standard (you’ve posted too much code) statement. I posted in two different Swift developer community slacks. And so far I am still stuck. I’ve tried setting the first responder, and that didn’t work. My next idea will be to remove the Geometry Reader that I use to size the City, State, Zip entries.

Will post when I figure this out.

Wasted Time for macOS updated

I just got notified that Wasted Time for macOS has made it thru the App Store review process. That means both the iOS and macOS versions have now been updated to support multiple languages. I am really excited by this and hope to add more languages as I go forward.

Currently, the app supports – German, Brazilian Portuguese, Spanish, Chinese (Hong Kong), Chinese (Simplified) and English. I am still experiencing an upload problem with the tvOS and watchOS versions.. but they will come soon.

App Store rejection

Well, Apple seems to have turned the screws tighter on this build of Wasted Time for the Mac. While I’ve had this out for a while, they noticed that if you closed the main window, there was no way to get it back. So the suggestion is to either add a Cmd-N (for new window) or have it completely terminate the app. I think I will make it terminate the app completely.

Localization and Internationalization

I’ve not had too much time to be able to work on my apps lately, even though I’ve been using my Holiday card tracker app a lot lately. It can be frustrating not to have the time to really dedicate learning new capabilities and APIs. On top of that, the paid (day) job has gotten really really busy for all the right reasons. So I shouldn’t complain.

Having said that I am very excited because I have amazing friend, who speak different languages, and I am ready to upload a new version of Wasted Time in multiple languages, with more languages to come!

I was hoping to make it available for watch and Apple TV too, but I am having some issues with Xcode. On the Apple TV it seems to have an issue with my graphics, which haven’t changed (so I am confused by that), and on the Apple Watch there are issues with the WKExtensionDelegate that I am working to figure out with Apple.

At least I will be able to help more people around the world be more productive!